Les données sont utilisées à chaque étape dans les flux de travail en sciences. Elles alimentent l’analyse et la modélisation. Les résultats qui en découlent sont aussi des données qui peuvent alimenter les travaux subséquents. Une bonne organisation des données facilitera le flux de travail.

Dicton. Proportions de temps voué aux calcul scientifique: 80% de nettoyage de données mal organisées, 20% de calcul.

Qu’est-ce qu’une donnée? De manière abstraite, il s’agit d’une valeur associée à une variable. Une variable peut être une dimension, une date, une couleur, le résultat d’un test statistique, à laquelle on attribue la valeur quantitative ou qualitative d’un chiffre, d’une chaîne de charactère, d’un symbole conventionné, etc. Par exemple, lorsque vous commandez un café latte végane, au latte est la valeur que vous attribuez à la variable type de café, et végane est la valeur de la variable type de lait.

L’exemple est peut être horrible. J’ai besoin d’un café…

Ce chapitre traite de l’importation, l’utilisation et l’exportation de données structurées, en R, sous forme de vecteurs, matrices, tableaux et ensemble de tableaux (bases de données).

Bien qu’il soit toujours préférable d’organiser les structures qui accueilleront les données d’une expérience avant-même de procéder à la collecte de données, l’analyste doit s’attendre à réorganiser ses données en cours de route. Or, des données bien organisées au départ faciliteront aussi leur réoganisation.

Ce chapitre débute avec quelques définitions: les données, les matrices, les tableaux et les bases de données, ainsi que leur signification en R. Puis nous verrons comment organiser un tableau selon quelques règles simples, mais importantes pour éviter les erreurs et les opérations fastidieuses pour reconstruire un tableau mal conçu. Ensuite, nous traiterons des formats de tableau courrant, pour enfin passer à l’utilisation de dplyr, le module tidyverse pour effectuer des opérations sur les tableaux.

Les collections de données

Dans le chapitre 2, nous avons survoler différents types d’objets: réels, entiers, chaînes de caractères et booléens. Les données peuvent appartenir à d’autres types: dates, catégories ordinales (ordonnées: faible, moyen, élevé) et nominales (non ordonnées: espèces, cultivars, couleurs, unité pédodlogique, etc.). Comme mentionné en début de chapitre, une donnée est une valeur associée à une variable. Les données peuvent être organisées en collections.

Nous avons vu au chapitre 2 que la manière privilégiée d’organiser des données était sous forme de tableau. De manière générale, un tableau de données est une organisation de données en deux dimensions, comportant des lignes et des colonnes. Il est préférable de respecter la convention selon laquelle les lignes sont des observations et les colonnes sont des variables. Ainsi, un tableau est une collection de vecteurs de même longueur, chaque vecteur représentant une variable. Chaque variable est libre de prendre le type de données approprié. La position d’une donnée dans le vecteur correspond à une observation.

Imaginez que vous consignez des données de différents sites (A, B et C), et que chaque site possède ses propres caractéristiques. Il est redondant de décrire le site pour chaque observation. Vous préférerez créer deux tableaux: un pour décrire vos observations, et un autre pour décrire les sites. De cette manière, vous créez une collection de tableaux intereliés: une base de données. R peut soutirer des données des bases de données grâce au module DBI, qui n’est pas couvert à ce stade de développement du cours.

Dans R, les données structurées en tableaux, ainsi que les opérations sur les tableaux, peuvent être gérés grâce aux modules readr, dplyr et tidyr. Mais avant de se lancer dans l’utilisation de ces modules, voyons quelques règles à suivre pour bien structurer ses données en format tidy, un du jargon tidyverse qui signifie proprement organisé.

Organiser un tableau de données

Afin de reprérer chaque cellule d’un tableau, on attribue à chaque lignes et à chaque colonne colonnes un identifiant unique, que l’on nomme indice pour les lignes et entête pour les colonnes.

Règle no 1. Une variable par colonne, une observation par ligne, une valeur par cellule.

Les unités expérimentales sont décrits par une ou plusieurs variables par des chiffres ou des lettres. Chaque variable devrait être présente en une seule colonne, et chaque ligne devrait correspondre à une unité expérimentale où ces variables ont été mesurées. La règle parait simple, mais elle est rarement respectée. Prenez par exemple le tableau suivant.

Site Traitement A Traitement B Traitement C
Sainte-Zéphirine 4.1 8.2 6.8
Sainte-Aurélie 5.8 5.9 NA
Saint-Serge-Étienne 2.9 3.4 4.6

Tableau 1. Rendements obtenus sur les sites expérimentaux selon les traitements.

Qu’est-ce qui cloche avec ce tableau? Chaque ligne est une observation, mais contient plussieurs observations d’une même variable, le rendement, qui devient étalé sur plusieurs colonnes. À bien y penser, le type de traitement est une variable et le rendement en est une autre:

Site Traitement Rendement
Sainte-Zéphirine A 4.1
Sainte-Zéphirine B 8.2
Sainte-Zéphirine C 6.8
Sainte-Aurélie A 5.8
Sainte-Aurélie B 5.9
Sainte-Aurélie C NA
Saint-Serge-Étienne A 2.9
Saint-Serge-Étienne B 3.4
Saint-Serge-Étienne C 4.6

Tableau 2. Rendements obtenus sur les sites expérimentaux selon les traitements.

Plus précisément, l’expression à bien y penser suggère une réflexion sur la signification des données. Certaines variables peuvent parfois être intégrées dans une même colonne, parfois pas. Par exemple, les concentrations en cuivre, zinc et plomb dans un sol contaminé peuvent être placés dans la même colonne “Concentration” ou déclinées en plusieurs colonnes Cu, Zn et Pb. La première version trouvera son utilité pour des créer des graphiques (chapitre 3), alors que la deuxième favorise le traitement statistique (chapitre 5). Il est possible de passer d’un format à l’autre grâce à la fonction gather() et spread() du module tidyr.

Règle no 2. Un tableau par unité observationnelle: ne pas répéter les informations.

Rerpenons la même expérience. Supposons que vous mesurez la précipitation à l’échelle du site.

Site Traitement Rendement Précipitations
Sainte-Zéphirine A 4.1 813
Sainte-Zéphirine B 8.2 813
Sainte-Zéphirine C 6.8 813
Sainte-Aurélie A 5.8 642
Sainte-Aurélie B 5.9 642
Sainte-Aurélie C NA 642
Saint-Serge-Étienne A 2.9 1028
Saint-Serge-Étienne B 3.4 1028
Saint-Serge-Étienne C 4.6 1028

Tableau 3. Rendements obtenus sur les sites expérimentaux selon les traitements.

Segmenter l’information en deux tableaux serait préférable.

Site Précipitations
Sainte-Zéphirine 813
Sainte-Aurélie 642
Saint-Serge-Étienne 1028

Tableau 4. Précipitations sur les sites expérimentaux.

Les tableaux 2 et 4, ensemble, forment une base de données (collection organisée de tableaux). Les opérations de fusion entre les tableaux peuvent être effectuées grâce aux fonctions de jointure (left_join(), par exemple) du module tidyr.

Règle no 3. Ne pas bousiller les données.

Par exemple.

Formats de tableau

Plusieurs outils sont à votre disposition pour créer des tableaux. Je vous présente ici les plus communs.

xls ou xlsx

Microsoft Excel est un logiciel de type tableur, ou chiffrier électronique. L’ancien format xls a été remplacé par le format xlsx avec l’arrivée de Microsoft Office 2010. Il s’agit d’un format propriétaire, dont l’alternative libre la plus connue est le format ods, popularisé par la suite bureautique LibreOffice. Les formats xls, xlsx ou ods sont davantage utilisés comme outils de calcul que d’entreposage de données. Ils contiennent des formules, des graphiques, du formattage de cellule, etc. Je ne les recommande pas pour stocker des données.

csv

Le format csv, pour comma separated values, est un fichier texte, que vous pouvez ouvrir avec n’importe quel éditeur de texte brut (Bloc note, Atom, Notepad++, etc.). Chaque colonne doit être délimitée par un caractère cohérent (conventionnellement une virgule, mais en français un point-virgule ou une tabulation pour éviter la confusion avec le séparateur décimal) et chaque ligne du tableau est un retour de ligne. Il est possible d’ouvrir et d’éditer les fichiers csv dans un éditeur texte, mais il est plus pratique de les ouvrir avec des tableurs (LibreOffice Calc, Microsoft Excel, Google Sheets, etc.).

Encodage des fichiers texte. Puisque le format csv est un fichier texte, un souci particulier doit être porté sur la manière dont le texte est encodé. Les caractères accentués pourrait être importer incorrectement si vous importez votre tableau en spécifiant le mauvais encodage. Pour les fichiers en langues occidentales, l’encodage UTF-8 devrait être utilisé. Toutefois, par défaut, Excel utilise un encodage de Microsoft. Si le csv a été généré par Excel, il est préférable de l’ouvrir avec votre éditeur texte et de l’enregistrer dans l’encodage UTF-8.

json

Comme le format csv, le format json indique un fichier en texte clair. Il est utilisé davantage pour le partage de données des applications web. En analyse et modélisation, ce format est surtout utilisé pour les données géoréférencées. L’encodage est géré de la même manière qu’un fichier csv.

SQLite

SQLite est une application pour les bases de données relationnelles de type SQL qui n’a pas besoin de serveur pour fonctionner. Les bases de donnnées SQLite sont encodés dans des fichiers portant l’extension db, qui peuvent être facilement partagés.

Suggestion

En csv pour les petits tableaux, en sqlite pour les bases de données plus complexes. Ce cours se concentre toutefois sur les données de type csv.

Entreposer ses données

La manière la plus sécure pour entreposer ses données est de les confiner dans une base de données sécurisée sur un serveur sécurisé dans un environnement sécurisé et d’encrypter les communications. C’est aussi la manière la moins accessible. Des espaces de stockage nuagiques, comme Dropbox ou d’autres options similaires (je suggère SpiderOak, qui est sécurisé), peuvent être pratiques pour les backups et le partage des données avec une équipe de travail (qui risque en retour de bousiller vos données). Le suivi de version est possible chez certains fournisseurs d’espace de stockage. Mais pour un suivi de version plus rigoureux, les espaces de développement (comme GitHub et GitLab) sont plus appropriés. Dans tous les cas, il est important de garder (1) des copies anciennes pour y revenir en cas d’erreurs et (2) un petit fichier décrivant les changements effectués sur les données.

Manipuler des données en mode tidyverse

Le méta-module tidyverse regroupe une collection de précieux modules pour l’analyse de données en R. Il permet d’importer des données dans votre session de travail avec readr, de les explorer avec ggplot2, de les transformer avec tidyr et dplyr et de les exporter aevc readr. Les tableaux de classe data.frame, comme ceux de la plus moderne classe tibble, peuvent être manipulés à travers le flux de travail pour l’analyse et la modélisation (chapitres suivants). Comme c’était le cas pour le chapitre sur la visualisation, ce chapitre est loin de couvrir les nombreuses fonctionnalités qui sont offertes dans le tidyverse.

Importer vos données dans voter session de travail

Supposons que vous avec bien organisé vos données en mode tidy. Pour les importer dans votre session et commencer à les inspecter, vous lancerez une des commandes du module readr, décrites dans la documentation dédiée.

  • read_csv() si le séparateur de colonne est une virgule
  • read_csv2() si le séparateur de colonne est un point-virgule et que le séparateur décimal est une virgule
  • read_tsv() si le séparateur de colonne est une tabulation
  • read_table() si le séparateur de colonne est un espace blanc
  • read_delim() si le séparateur de colonne est un autre caractère (comme le point-virgule) que vous spécifierez dans l’argument delim = ";"

Les principaux arguments sont les suivants.

  • file: le chemin vers le fichier. Ce chemin peut aussi bien être une adresse locale (data/…) qu’une adresse internet (https://…).
  • delim: le symbole délimitant les colonnes dans le cas de read_delim.
  • col_names: si TRUE, la première ligne est l’entête du tableau, sinon FALSE. Si vous spécifiez un vecteur numérique, ce sont les numéros des lignes utilisées pour le nom de l’entête. Si vous utilisez un vecteur de charactères, ce sont les noms des colonnes que vous désirez donner à votre tableau.
  • na: le symbole spécifiant une valeur manquante. L’argument na='' signifie que les cellules vides sont des données manquantes. Si les valeurs manquantes ne sont pas uniformes, vous pouvez les indiquer dans un vecteur, par exemple na = c("", "NA", "NaN", ".", "-").
  • local: cet argument prend une fonction local() qui peut inclure des arguments de format de temps, mais aussi d’encodage (voir documentation)

D’autres arguments peuvent être spécifiés au besoin, et les répéter ici dupliquerait l’information de la documentation de la fonction read_csv de readr.

Je déconseille d’importer des données en format xls ou xlsx. Si toutefois cela vous convient, je vous réfère au module readxl.

L’aide-mémoire de readr est à afficher près de soi.

Aide-mémoire de readr, source: https://www.rstudio.com/resources/cheatsheets/

Nous allons charger des données de culture de la chicouté (Rubus chamaemorus), un petit fruit nordique, tiré de Parent et al. (2013). Ouvrons d’abord le fichier pour vérifier les séparateurs de colonne et de décimale.

Le séparateur de colonne est un point-virgule et le décimal est une virgule.

Avec Atom, mon éditeur texte préféré, je vais dans Edit > Select Encoding et j’obtiens bien le UTF-8.

Nous allons donc utiliser read_csv2() avec ses arguments par défaut.

library("tidyverse")
chicoute <- read_csv2('data/chicoute.csv')
Using ',' as decimal and '.' as grouping mark. Use read_delim() for more control.
Parsed with column specification:
cols(
  .default = col_double(),
  ID = col_integer(),
  CodeTourbiere = col_character(),
  Ordre = col_character(),
  Site = col_integer(),
  Traitement = col_character(),
  DemiParcelle = col_character(),
  SousTraitement = col_character(),
  Latitude_m = col_integer(),
  Longitude_m = col_integer()
)
See spec(...) for full column specifications.

Quelques commandes utiles inspecter le tableau:

  • head() présente l’entête du tableau, soit ses 6 premières lignes
  • str() et glimpse() présentent les variables du tableau et leur type - glimpse()est la fonction tidyverse et str() est la fonction classique (je préfère str())
  • summary() présente des statistiques de base du tableau
  • names() ou colnames() sort les noms des colonnes sous forme d’un vecteur
  • dim() donne les dimensions du tableau, ncol() son nombre de colonnes et nrow() son nombre de lignes
  • skim est une fonction du module skimr montrant un portrait graphique et numérique du tableau

Extra 1. Plusieurs modules ne se trouvent pas dans les dépôt CRAN, mais sont disponibles sur GitHub. Pour les installer, installez d’abord le module devtools disponible sur CRAN. Vous pourrez alors installer les packages de GitHub comme on le fait avec le package skimr.

Extra 2. Lorsque je désire utiliser une fonction, mais sans charger le module dans la session, j’utilise la notation module::fonction. Comme dans ce cas, pour skimr.

# devtools::install_github("ropenscilabs/skimr")
skimr::skim(chicoute)
Skim summary statistics
 n obs: 90 
 n variables: 31 

── Variable type:character ───────────────────────────────────────────────────────────────
       variable missing complete  n min max empty n_unique
  CodeTourbiere       0       90 90   1   4     0       12
   DemiParcelle      50       40 90   4   5     0        2
          Ordre       0       90 90   1   2     0       20
 SousTraitement      50       40 90   1   7     0        3
     Traitement      50       40 90   6  11     0        2

── Variable type:integer ─────────────────────────────────────────────────────────────────
    variable missing complete  n       mean      sd      p0        p25       p50
          ID       0       90 90      45.5    26.12       1      23.25      45.5
  Latitude_m       0       90 90 5701839.86 1915.5  5695688 5701868.5  5702129  
 Longitude_m       0       90 90  485295.54 6452.33  459873  485927     486500  
        Site       0       90 90       6.33    5.49       1       2          4  
        p75    p100     hist
      67.75      90 ▇▇▇▇▇▇▇▇
 5702537    5706394 ▁▁▁▂▇▆▁▁
  486544.75  491955 ▁▁▁▁▁▁▇▁
       9         20 ▇▅▁▁▁▁▁▁

── Variable type:numeric ─────────────────────────────────────────────────────────────────
                 variable missing complete  n      mean        sd          p0       p25
                 Al_pourc       0       90 90   0.0027    0.0013  9e-04         0.0019 
                  B_pourc       0       90 90   0.0031    0.00067     0.0018    0.0026 
                  C_pourc       0       90 90  50.28      1.61       46.72     49.14   
                 Ca_pourc       0       90 90   0.39      0.1         0.19      0.32   
                 Cu_pourc       0       90 90   0.00041   0.00064     3.7e-05   3.7e-05
                 Fe_pourc       0       90 90   0.015     0.0059      0.0091    0.011  
  FemelleAvorte_nombre_m2       4       86 90   8.49     14.52        0         1.27   
   FemelleFruit_nombre_m2      18       72 90  19.97     23.79        0.4       7.64   
                  K_pourc       0       90 90   0.89      0.27        0.35      0.69   
                 Mg_pourc       0       90 90   0.5       0.085       0.36      0.45   
                 Mn_pourc       0       90 90   0.033     0.025       0.0023    0.012  
                  N_pourc       0       90 90   2.2       0.4         1.53      1.89   
                  P_pourc       0       90 90   0.14      0.037       0.071     0.12   
          Rendement_g_5m2      50       40 90  13.33     21.56        0         0      
                  S_pourc       0       90 90   0.13      0.039       0.07      0.11   
   SterileFleur_nombre_m2       4       86 90   0.26      0.71        0         0      
   TotalFemelle_nombre_m2       4       86 90  27.53     29.83        2.55     10.34   
    TotalFloral_nombre_m2       4       86 90  52.08     40.41        4.8      22.92   
      TotalMale_nombre_m2       4       86 90  24.4      26.87        0         3.3    
     TotalRamet_nombre_m2       0       90 90 251.26    156.06       40.74    122.7    
 TotalVegetatif_nombre_m2       4       86 90 199.02    139.13       22.92     86.26   
                 Zn_pourc       0       90 90   0.0067    0.0021      0.0033    0.0055 
       p50       p75     p100     hist
   0.0024    0.0033    0.0093 ▆▇▅▂▁▁▁▁
   0.0032    0.0035    0.0042 ▃▂▅▃▃▇▁▅
  50.45     51.58     53.83   ▂▃▆▃▅▇▂▁
   0.37      0.44      0.88   ▂▇▇▃▂▁▁▁
   0.00021   0.00046   0.0042 ▇▁▁▁▁▁▁▁
   0.014     0.017     0.052  ▇▅▂▁▁▁▁▁
   3.07     10.14     76.8    ▇▂▁▁▁▁▁▁
  11.46     22.83    157.88   ▇▂▁▁▁▁▁▁
   0.86      1.13      1.54   ▃▂▇▆▃▆▂▁
   0.48      0.52      0.86   ▃▇▆▂▁▁▁▁
   0.028     0.05      0.1    ▇▃▃▃▂▁▁▁
   2.12      2.58      3.1    ▃▆▇▆▂▅▃▂
   0.14      0.16      0.23   ▆▂▇▇▇▃▂▁
   0.95     15.63     72.44   ▇▁▁▁▁▁▁▁
   0.13      0.14      0.28   ▂▆▇▂▁▁▁▁
   0         0         3.82   ▇▁▁▁▁▁▁▁
  17.19     31.96    187.17   ▇▂▁▁▁▁▁▁
  43        69.52    198.62   ▇▇▃▂▁▁▁▁
  15.28     36.51    104.41   ▇▃▂▂▁▁▁▁
 212.92    347.8     651.9    ▇▃▇▂▂▂▂▁
 161.25    263.78    580.6    ▇▇▆▃▂▂▂▁
   0.0063    0.0072    0.016  ▂▇▅▁▁▁▁▁

Exercice. Inspectez le tableau.

Comment sélectionner et filtrer des données?

On utiliser le terme sélectionner lorsque l’on désire choisir une ou plusieurs lignes et colonnes d’un tableau (la plupart du temps des colonnes). L’action de filtrer signifie de sélectionner des lignes selon certains critères.

Sélectionner

Voici trois manières de sélectionner une colonne en R.

  • Une méthode rapide mais peu expressive consiste à indiquer les valeurs numériques de l’indice de la colonne entre des crochets. Il s’agit d’appeler le tableau suivit de crochets. L’intérieur des crochets comprend deux éléments séparés par une virgule. Le premier élément sert à filter selon l’indice, le deuxième sert à sélectionner selon l’indice. Ainsi:
  • chicoute[, 1]: sélectionner la première colonne
  • chicoute[, 1:10]: sélectionner les 10 premières colonnes
  • chicoute[, c(2, 4, 5)]: sélectionner les colonnes 2, 4 et 5
  • chicoute[c(10, 13, 20), c(2, 4, 5)]: sélectionner les colonnes 2, 4 et 5 et les lignes 10, 13 et 20.

  • Une autre méthode rapide, mais plus expressive, consiste à appeler le tableau, suivi du symbole $, puis le nom de la colonne.

Truc. La plupart des IDE, comme RStudio, peuvent vous proposer des colonnes dans une liste. Après avoir entrer le $, taper sur la touche de tabulation: vous pourrez sélectionner la colonne dans une liste défilante.

  • Une autre option est d’inscrire le nom de la colonne, ou du vecteur des colonnes, entre des crochets suivant le nom du tableau, c’est-à-dire chicoute[c("Site", "Latitude_m", "Longitude_m")].

  • Enfin, dans une séquence d’opérations en mode pipeline (chaque opération est mise à la suite de la précédente en plaçant le pipe %>% entre chacune), il peut être préférable de sélectionner des colonnes avec la fonction select(), i.e.

chicoute %>%
select(Site, Latitude_m, Longitude_m)

La fonction select() permet aussi de travailler en exclusion. Ainsi pour enlever des colonnes, on placera un - (signe de soustraction) devant le nom de la colonne.

D’autre arguments de select() permettent une sélection rapide. Par exemple, pour obtenir les colonnes contenant des pourcentages:

chicoute %>%
  select(ends_with("pourc")) %>%
  head(3)

Filtrer

Comme c’est le cas de la sélection, on pourra filtrer un tableau de plusieurs manières. J’ai déjà présenté comment filtrer selon les indices des lignes. Les autres manières reposent néanmoins sur une opération logique ==, <, > ou %in% (le %in% signifie se trouve parmi et peut être suivi d’un vecteur de valeur que l’on désire accepter).

Les conditions booléennes peuvent être combinées avec les opérateurs et, &, et ou, |. Pour rappel,

Opération Résultat
Vrai et Vrai Vrai
Vrai et Faux Faux
Faux et Faux Faux
Vrai ou Vrai Vrai
Vrai ou Faux Vrai
Faux ou Faux Faux
  • La méthode classique consiste à appliquer une opération logique entre les crochets, par exemple chicoute[chicoute$CodeTourbiere == "BEAU", ]
  • La méthode tidyverse, plus pratique en mode pipeline, passe par la fonction filter(), i.e.
chicoute %>%
filter(CodeTourbiere == "BEAU")

Combiner le tout.

chicoute %>%
  filter(Ca_pourc < 0.4 & CodeTourbiere %in% c("BEAU", "MB", "WTP")) %>%
  select(contains("pourc"))

Le format long et le format large

Dans le tableau chicoute, chaque élément possède sa propre colonne. Si l’on voulait mettre en graphique les boxplot des facettes de concentrations d’azote, de phosphore et de potassium dans les différentes tourbières, il faudrait obtenir une seule colonne de concentrations.

Pour ce faire, nous utiliserons la fonction gather(). Le premier argument est le nom de la colonne des variables, le deuxième est le nom de la nouvelle colonne des valeurs. La suite consiste à décrire les colonnes à inclure ou à exclulre. Dans le cas qui suit, j’exclue CodeTourbiere de la refonte j’utilise sample_n() pour présenter un échantillon du résultat.

chicoute_long <- chicoute %>%
  select(CodeTourbiere, N_pourc, P_pourc, K_pourc) %>%
  gather(key = element, value = concentration, -CodeTourbiere)
chicoute_long %>% sample_n(10)

L’opération inverse est spread(). Pour que cette opération fonctionne, spread() a besoin d’une colonne ayant un identifiant unique.

chicoute_long$ID <- 1:nrow(chicoute_long)

Nous pouvons enlever cet identifiant une fois l’opération effectuée.

chicoute_large <- chicoute_long %>%
  spread(key=element, value=concentration, fill=0) %>%
  select(-ID)
chicoute_large %>% sample_n(10)

Sans créer de nouveau tableau, il est possible de créer le graphique dans un seu pipeline.

chicoute %>%
  select(CodeTourbiere, N_pourc, P_pourc, K_pourc) %>%
  gather(key = element, value = concentration, -CodeTourbiere) %>%
  ggplot(mapping = aes(x = CodeTourbiere, y = concentration)) +
  facet_grid(element ~ ., scales = 'free') +
  geom_boxplot()

Combiner des tableaux

Nous avons introduit plus haut la notion de base de données. Nous voudrions peut-être utiliser le code des tourbières pour inclure leur nom, le type d’essai mené à ces tourbières, etc. Importons d’abord le tableau des noms liés aux codes.

tourbieres <- read_csv2("data/chicoute_tourbieres.csv")
Using ',' as decimal and '.' as grouping mark. Use read_delim() for more control.
Parsed with column specification:
cols(
  Tourbiere = col_character(),
  CodeTourbiere = col_character(),
  Type = col_character(),
  TypeCulture = col_character()
)
tourbieres

Notre information est organisée en deux tableaux, liés par la colonne CodeTourbiere. Comment fusionner l’information pour qu’elle puisse être utilisée dans son ensemble? La fonction left_join effectue cette opération typique avec les bases de données.

chicoute_merge <- left_join(x = chicoute, y = tourbieres, by = "CodeTourbiere")
# ou bien chicoute %>% left_join(y = tourbieres, by = "CodeTourbiere")
chicoute_merge %>% sample_n(4)

D’autres types de jointures sont possibles, et décrites en détails dans la documentation.

Garrick Aden-Buie a préparé de jolies animations pour décrire les différents types de jointures.

left_join(x, y) colle y à x seulement ce qui dans y correspond à ce que l’on trouve dans x.

right_join(x, y) colle y à x seulement ce qui dans x correspond à ce que l’on trouve dans y.

inner_join(x, y) colle x et y en excluant les lignes où au moins une variable de joint est absente dans x et y.

full_join(x, y)garde toutes les lignes et les colonnes de x et y.

Opérations sur les tableaux

Les tableaux peuvent être segmentés en éléments sur lesquels on calculera ce qui nous chante.

On pourrait vouloir obtenir:

  • la somme avec la function sum()
  • la moyenne avec la function mean() ou la médiane avec la fonction median()
  • l’écart-type avec la function sd()
  • les maximum et minimum avec les fonctions min() et max()
  • un décompte d’occurence avec la fonction n() ou count()

Par exemple,

mean(chicoute$Rendement_g_5m2, na.rm = TRUE)
[1] 13.32851

En mode classique, pour effectuer des opérations sur des tableaux, on utilisera la fonction apply(). Cette fonction prend, comme arguments, le tableau, l’axe (opération par ligne = 1, opération par colonne = 2), puis la fonction à appliquer.

apply(chicoute %>% select(contains("pourc")), 2, mean)
     C_pourc      N_pourc      P_pourc      K_pourc     Ca_pourc     Mg_pourc 
5.027911e+01 2.199411e+00 1.388959e-01 8.887000e-01 3.884391e-01 4.980142e-01 
     S_pourc      B_pourc     Cu_pourc     Zn_pourc     Mn_pourc     Fe_pourc 
1.347177e-01 3.090922e-03 4.089891e-04 6.662155e-03 3.345239e-02 1.514885e-02 
    Al_pourc 
2.694979e-03 

Les opération peuvent aussi être effectuées par ligne, par exemple une somme (je garde seulement les 10 premiers résultats).

apply(chicoute %>% select(contains("pourc")), 1, sum)[1:10]
 [1] 55.64299 55.76767 54.78856 55.84453 57.89671 55.53603 55.62526 55.10991 55.06295
[10] 55.16774

La fonction à appliquer peut être personnalisée, par exemple:

apply(chicoute %>% select(contains("pourc")), 2,
      function(x) (prod(x))^(1/length(x)))
     C_pourc      N_pourc      P_pourc      K_pourc     Ca_pourc     Mg_pourc 
50.253429104  2.165246915  0.133754530  0.846193827  0.376192724  0.491763884 
     S_pourc      B_pourc     Cu_pourc     Zn_pourc     Mn_pourc     Fe_pourc 
 0.129900753  0.003014675  0.000000000  0.006408775  0.024140327  0.014351745 
    Al_pourc 
 0.002450982 

Vous reconnaissez cette fonction? C’était la moyenne géométrique (la fonction prod() étant le produit d’un vecteur).

En mode tidyverse, on aura besoin principalement des fonction suivantes:

  • group_by() pour effectuer des opérations par groupe, l’oprération group_by() sépare le tableau en plusieurs petits tableaux, en attendant de les recombiner. C’est un peu l’équivalent des facettes en ggplot2…
  • summarise() pour réduire plusieurs valeurs en une seule, il applique un calcul sur le tableau ou s’il y a lieu sur chaque petit tableau segmenté. Il en existe quelques variantes.
    • summarise_all() applique la fonction à toutes les colonnes
    • summarise_at() applique la fonction aux colonnes spécifiées
    • summarise_if() applique la fonction aux colonnes qui resortent comme TRUE selon une opération booléenne
  • mutate() pour ajotuer une nouvelle colonne
    • Si l’on désire ajouter une colonne à un tableau, par exemple le sommaire calculé avec summarise(). À l’inverse, la fonction transmute() retournera seulement le résultat, sans le tableau à partir duquel il a été calculé. De même que summarise(), mutate() et transmute() possèdent leurs équivalents _all(), _at() et _if().
  • arrange() pour réordonner le tableau
    • On a déjà couvert arrange() dans le chapitre 3. Rappelons que cette fonction n’est pas une opération sur un tableau, mais plutôt un changement d’affichage en changeant l’ordre d’apparition des données.

Ces opérations sont décrites dans l’aide-mémoire Data Transformation Cheat Sheet.

Aide-mémoire de dplyr, source: https://www.rstudio.com/resources/cheatsheets/

Pour effectuer des statistiques par colonne, on utilisera summarise_all() étant donnée que l’on désire un sommaire sur toutes les variables sélectionnées. Pour spécifier que l’on désire la moyenne et l’écart-type on inscrit les noms des fonctions dans funs().

chicoute %>%
  select(contains("pourc")) %>%
  summarise_all(funs(mean, sd))

On utilisera group_by() pour segmenter le tableau, et ainsi obtenir des statistiques pour chaque groupe.

chicoute %>%
  group_by(CodeTourbiere) %>%
  select(contains("pourc")) %>%
  summarise_all(funs(mean, sd))
Adding missing grouping variables: `CodeTourbiere`

Pour obtenir des statistiques à chaque ligne, mieux vaut utiliser apply(), tel que vu précédemment. Le point, ., représente le tableau dans la fonction.

chicoute %>%
  select(contains("pourc")) %>%
  apply(., 1, sum)
 [1] 55.64299 55.76767 54.78856 55.84453 57.89671 55.53603 55.62526 55.10991 55.06295
[10] 55.16774 56.41123 55.47917 55.43537 55.79175 55.44561 54.85448 54.34262 55.03075
[19] 54.40533 51.89319 54.70172 54.62176 54.30250 53.86976 53.44731 53.86244 52.43280
[28] 54.34978 53.96756 51.46672 55.44267 54.70350 55.30711 56.16200 56.64710 55.95499
[37] 54.76370 54.32775 54.95419 53.37094 53.07855 53.04541 52.09520 52.40456 51.92376
[46] 53.33248 56.56405 56.35004 56.27185 55.56986 53.81654 55.39638 55.51961 54.88098
[55] 54.74774 51.08921 51.31462 53.46819 53.15640 52.82020 57.78038 57.94636 56.65558
[64] 56.28845 55.54463 56.51751 55.36497 56.00594 55.64247 56.56967 56.81674 55.87070
[73] 55.72308 56.14116 56.42611 55.35650 54.90469 54.03674 53.42991 53.99334 53.09085
[82] 53.23222 53.28212 53.63192 53.48102 52.31131 51.72026 51.10534 51.49055 51.59297

Revenons à notre tableau des especes meancées.

especes_menacees <- read_csv('data/WILD_LIFE_16082018051732754.csv')
Parsed with column specification:
cols(
  IUCN = col_character(),
  `IUCN Category` = col_character(),
  SPEC = col_character(),
  Species = col_character(),
  COU = col_character(),
  Country = col_character(),
  `Unit Code` = col_character(),
  Unit = col_character(),
  `PowerCode Code` = col_integer(),
  PowerCode = col_character(),
  `Reference Period Code` = col_character(),
  `Reference Period` = col_character(),
  Value = col_integer(),
  `Flag Codes` = col_character(),
  Flags = col_character()
)

Nous avions exécuté le pipeline suivant.

especes_menacees %>%
  filter(IUCN == 'CRITICAL') %>%
  select(Country, Value) %>%
  group_by(Country)  %>%
  summarise(n_critical_species = sum(Value)) %>%
  arrange(desc(n_critical_species)) %>%
  top_n(10)
Selecting by n_critical_species

Ce pipeline consistait à:

prendre le tableau especes_menacees, puis
  filtrer pour n'obtenir que les espèces critiques, puis
  sélectionner les colonnes des pays et des valeurs (nombre d'espèces), puis
  segmenter le tableaux en plusieurs tableaux selon le pays, puis
  appliquer la fonction sum pour chacun de ces petits tableaux (puis de recombiner ces sommaires), puis
  trier les pays en nombre décroissant de décompte d'espèces, puis
  afficher le top 10

Exemple (difficile)

Pour revenir à notre tableau chicoute, imaginez que vous aviez une station météo (station_A) située aux coordonnées (490640, 5702453) et que vous désiriez calculer la distance entre l’observation et la station. Prenez du temps pour réfléchir à la manière dont vous procéderez…

On pourra créer une fonction qui mesure la distance entre un point x, y et les coordonnées de la station A…

dist_station_A <- function (x, y) {
  return(sqrt((x - 490640)^2 + (y - 5702453)^2))
}

… puis ajouter une colonne avec mutate grâce à une fonction prenant les arguments x et y spécifiés.

chicoute %>%
  mutate(dist = dist_station_A(x = Longitude_m, y= Latitude_m)) %>%
  select(ID, CodeTourbiere, Longitude_m, Latitude_m, dist) %>%
  top_n(10)
Selecting by dist

Nous pourrions procéder de la même manière pour fusionner des données climatiques. Le tableau chicoute ne possède pas d’indicateurs climatiques, mais il est possible de les soutirer de stations météos placées près des site. Ces données ne sont pas disponibles pour le tableau de la chicouté, alors j’utiliserai des données fictives pour l’exemple.

Voici ce qui pourrait être fait.

  1. Créer un tableau des stations météo ainsi que des indices météo associés à ces stations.
  2. Lier chaque site à une station (à la main où selon la plus petite distance entre le site et la station).
  3. Fusionner les inices climatiques aux sites, puis les sites aux mesures de rendement.

Ces opérations demandent habituellement du tâtonnement. Il serait surprenant que même une personne expérimentée soit en mesure de compiler ces opérations sans obtenir de message d’erreur, et retravailler jusqu’à obtenir le résultat souhaité. L’objectif de cette section est de vous présenté un flux de travail que vous pourriez être amenés à effectuer et de fournir quelques éléments nouveau pour mener à bien une opération. Il peut être frustant de ne pas saisir toutes les opérations: passez à travers cette section sans jugement. Si vous devez vous frotter à problème semblable, vous saurez que vous trouverez dans ce manuel une recette intéressante.

stations <- data.frame(Station = c('A', 'B', 'C'),
                       Longitude_m = c(490640, 484870, 485929),
                       Latitude_m = c(5702453, 5701870, 5696421), 
                       t_moy_C = c(13.8, 18.2, 16.30),
                       prec_tot_mm = c(687, 714, 732))
stations

La fonction suivante calcule la distance entre des coordonnées x et y et chaque station d’un tableau de stations, puis retourne le nom de la station dont la distance est la moindre.

dist_station <- function (x, y, stations_df) {
    # stations est le tableau des stations à trois colonnes
    # 1iere: nom de la station
    # 2ieme: longitude
    # 3ieme: latitude
    distance <- c()
    for (i in 1:nrow(stations)) {
        distance[i] <- sqrt((x - stations[i, 2])^2 + (y - stations[i, 3])^2)
    }
    nom_station <- as.character(stations$Station[which.min(distance)])
    return(nom_station)
}

Testons la fonction avec des coordonnées.

dist_station(x = 459875, y = 5701988, stations_df = stations)
[1] "B"

Nous appliquons cette fonction à toutes les lignes du tableau, puis en retournons un échantillon.

chicoute %>%
    rowwise() %>%
    mutate(Station = dist_station(x = Longitude_m, y = Latitude_m, stations_df = stations)) %>%
    select(ID, CodeTourbiere, Longitude_m, Latitude_m, Station) %>%
    sample_n(10)

Cela semble fonctionner. On peut y ajouter un left_join() pour joindre les données météo au tableau principal.

chicoute_weather <- chicoute %>%
    rowwise() %>%
    mutate(Station = dist_station(x = Longitude_m, y = Latitude_m, stations_df = stations)) %>%
    left_join(y = stations, by = "Station")
Column `Station` joining character vector and factor, coercing into character vector
chicoute_weather %>% sample_n(10)

Exporter un tableau

Simplement avec write_csv().

write_csv(chicoute_weather, "data/chicoute_weather.csv")

Aller plus loin dans le tidyverse

Le livre R for Data Science, de Garrett Grolemund et Hadley Wickham, est un incontournable.

Références

Parent L.E., Parent, S.É., Herbert-Gentile, V., Naess, K. et Lapointe, L. 2013. Mineral Balance Plasticity of Cloudberry (Rubus chamaemorus) in Quebec-Labrador Bogs. American Journal of Plant Sciences, 4, 1508-1520. DOI: 10.4236/ajps.2013.47183

LS0tCnRpdGxlOiAiT3JnYW5pc2F0aW9uIGRlcyBkb25uw6llcyBldCBvcMOpcmF0aW9ucyBzdXIgZGVzIHRhYmxlYXV4IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpMZXMgZG9ubsOpZXMgc29udCB1dGlsaXPDqWVzIMOgIGNoYXF1ZSDDqXRhcGUgZGFucyBsZXMgZmx1eCBkZSB0cmF2YWlsIGVuIHNjaWVuY2VzLiBFbGxlcyBhbGltZW50ZW50IGwnYW5hbHlzZSBldCBsYSBtb2TDqWxpc2F0aW9uLiBMZXMgcsOpc3VsdGF0cyBxdWkgZW4gZMOpY291bGVudCBzb250IGF1c3NpIGRlcyBkb25uw6llcyBxdWkgcGV1dmVudCBhbGltZW50ZXIgbGVzIHRyYXZhdXggc3Vic8OpcXVlbnRzLiBVbmUgYm9ubmUgb3JnYW5pc2F0aW9uIGRlcyBkb25uw6llcyBmYWNpbGl0ZXJhIGxlIGZsdXggZGUgdHJhdmFpbC4KCj4gKipEaWN0b24qKi4gUHJvcG9ydGlvbnMgZGUgdGVtcHMgdm91w6kgYXV4IGNhbGN1bCBzY2llbnRpZmlxdWU6IDgwJSBkZSBuZXR0b3lhZ2UgZGUgZG9ubsOpZXMgbWFsIG9yZ2FuaXPDqWVzLCAyMCUgZGUgY2FsY3VsLgoKUXUnZXN0LWNlIHF1J3VuZSBkb25uw6llPyBEZSBtYW5pw6hyZSBhYnN0cmFpdGUsIGlsIHMnYWdpdCBkJ3VuZSB2YWxldXIgYXNzb2Npw6llIMOgIHVuZSB2YXJpYWJsZS4gVW5lIHZhcmlhYmxlIHBldXQgw6p0cmUgdW5lIGRpbWVuc2lvbiwgdW5lIGRhdGUsIHVuZSBjb3VsZXVyLCBsZSByw6lzdWx0YXQgZCd1biB0ZXN0IHN0YXRpc3RpcXVlLCDDoCBsYXF1ZWxsZSBvbiBhdHRyaWJ1ZSBsYSB2YWxldXIgcXVhbnRpdGF0aXZlIG91IHF1YWxpdGF0aXZlIGQndW4gY2hpZmZyZSwgZCd1bmUgY2hhw65uZSBkZSBjaGFyYWN0w6hyZSwgZCd1biBzeW1ib2xlIGNvbnZlbnRpb25uw6ksIGV0Yy4gUGFyIGV4ZW1wbGUsIGxvcnNxdWUgdm91cyBjb21tYW5kZXogdW4gY2Fmw6kgKmxhdHRlKiB2w6lnYW5lLCAqYXUgbGF0dGUqIGVzdCBsYSB2YWxldXIgcXVlIHZvdXMgYXR0cmlidWV6IMOgIGxhIHZhcmlhYmxlICp0eXBlIGRlIGNhZsOpKiwgZXQgKnbDqWdhbmUqIGVzdCBsYSB2YWxldXIgZGUgbGEgdmFyaWFibGUgKnR5cGUgZGUgbGFpdCouCgpMJ2V4ZW1wbGUgZXN0IHBldXQgw6p0cmUgaG9ycmlibGUuIEonYWkgYmVzb2luIGQndW4gY2Fmw6kuLi4KCiFbXShodHRwczovL21lZGlhLmdpcGh5LmNvbS9tZWRpYS8zbmJ4eXBUMjBVbG1vL2dpcGh5LmdpZikKCkNlIGNoYXBpdHJlIHRyYWl0ZSBkZSBsJ2ltcG9ydGF0aW9uLCBsJ3V0aWxpc2F0aW9uIGV0IGwnZXhwb3J0YXRpb24gZGUgZG9ubsOpZXMgc3RydWN0dXLDqWVzLCBlbiBSLCBzb3VzIGZvcm1lIGRlIHZlY3RldXJzLCBtYXRyaWNlcywgdGFibGVhdXggZXQgZW5zZW1ibGUgZGUgdGFibGVhdXggKGJhc2VzIGRlIGRvbm7DqWVzKS4KCkJpZW4gcXUnaWwgc29pdCB0b3Vqb3VycyBwcsOpZsOpcmFibGUgZCdvcmdhbmlzZXIgbGVzIHN0cnVjdHVyZXMgcXVpIGFjY3VlaWxsZXJvbnQgbGVzIGRvbm7DqWVzIGQndW5lIGV4cMOpcmllbmNlIGF2YW50LW3Dqm1lIGRlIHByb2PDqWRlciDDoCBsYSBjb2xsZWN0ZSBkZSBkb25uw6llcywgbCdhbmFseXN0ZSBkb2l0IHMnYXR0ZW5kcmUgw6AgcsOpb3JnYW5pc2VyIHNlcyBkb25uw6llcyBlbiBjb3VycyBkZSByb3V0ZS4gT3IsIGRlcyBkb25uw6llcyBiaWVuIG9yZ2FuaXPDqWVzIGF1IGTDqXBhcnQgZmFjaWxpdGVyb250IGF1c3NpIGxldXIgcsOpb2dhbmlzYXRpb24uCgpDZSBjaGFwaXRyZSBkw6lidXRlIGF2ZWMgcXVlbHF1ZXMgZMOpZmluaXRpb25zOiBsZXMgZG9ubsOpZXMsIGxlcyBtYXRyaWNlcywgbGVzIHRhYmxlYXV4IGV0IGxlcyBiYXNlcyBkZSBkb25uw6llcywgYWluc2kgcXVlIGxldXIgc2lnbmlmaWNhdGlvbiBlbiBSLiBQdWlzIG5vdXMgdmVycm9ucyBjb21tZW50IG9yZ2FuaXNlciB1biB0YWJsZWF1IHNlbG9uIHF1ZWxxdWVzIHLDqGdsZXMgc2ltcGxlcywgbWFpcyBpbXBvcnRhbnRlcyBwb3VyIMOpdml0ZXIgbGVzIGVycmV1cnMgZXQgbGVzIG9ww6lyYXRpb25zIGZhc3RpZGlldXNlcyBwb3VyIHJlY29uc3RydWlyZSB1biB0YWJsZWF1IG1hbCBjb27Dp3UuIEVuc3VpdGUsIG5vdXMgdHJhaXRlcm9ucyBkZXMgZm9ybWF0cyBkZSB0YWJsZWF1IGNvdXJyYW50LCBwb3VyIGVuZmluIHBhc3NlciDDoCBsJ3V0aWxpc2F0aW9uIGRlIFtgZHBseXJgXShodHRwOi8vcGFuZGFzLnB5ZGF0YS5vcmcpLCBsZSBtb2R1bGUgdGlkeXZlcnNlIHBvdXIgZWZmZWN0dWVyIGRlcyBvcMOpcmF0aW9ucyBzdXIgbGVzIHRhYmxlYXV4LgoKIyBMZXMgY29sbGVjdGlvbnMgZGUgZG9ubsOpZXMKCkRhbnMgbGUgY2hhcGl0cmUgMiwgbm91cyBhdm9ucyBzdXJ2b2xlciBkaWZmw6lyZW50cyB0eXBlcyBkJ29iamV0czogcsOpZWxzLCBlbnRpZXJzLCBjaGHDrm5lcyBkZSBjYXJhY3TDqHJlcyBldCBib29sw6llbnMuIExlcyBkb25uw6llcyBwZXV2ZW50IGFwcGFydGVuaXIgw6AgZCdhdXRyZXMgdHlwZXM6IGRhdGVzLCBjYXTDqWdvcmllcyBvcmRpbmFsZXMgKG9yZG9ubsOpZXM6IGZhaWJsZSwgbW95ZW4sIMOpbGV2w6kpIGV0IG5vbWluYWxlcyAobm9uIG9yZG9ubsOpZXM6IGVzcMOoY2VzLCBjdWx0aXZhcnMsIGNvdWxldXJzLCB1bml0w6kgcMOpZG9kbG9naXF1ZSwgZXRjLikuIENvbW1lIG1lbnRpb25uw6kgZW4gZMOpYnV0IGRlIGNoYXBpdHJlLCB1bmUgZG9ubsOpZSBlc3QgdW5lIHZhbGV1ciBhc3NvY2nDqWUgw6AgdW5lIHZhcmlhYmxlLiBMZXMgZG9ubsOpZXMgcGV1dmVudCDDqnRyZSBvcmdhbmlzw6llcyBlbiBjb2xsZWN0aW9ucy4KCk5vdXMgYXZvbnMgdnUgYXUgY2hhcGl0cmUgMiBxdWUgbGEgbWFuacOocmUgcHJpdmlsw6lnacOpZSBkJ29yZ2FuaXNlciBkZXMgZG9ubsOpZXMgw6l0YWl0IHNvdXMgZm9ybWUgZGUgKip0YWJsZWF1KiouIERlIG1hbmnDqHJlIGfDqW7DqXJhbGUsIHVuIHRhYmxlYXUgZGUgZG9ubsOpZXMgZXN0IHVuZSBvcmdhbmlzYXRpb24gZGUgZG9ubsOpZXMgZW4gZGV1eCBkaW1lbnNpb25zLCBjb21wb3J0YW50IGRlcyAqbGlnbmVzKiBldCBkZXMgKmNvbG9ubmVzKi4gSWwgZXN0IHByw6lmw6lyYWJsZSBkZSByZXNwZWN0ZXIgbGEgY29udmVudGlvbiBzZWxvbiBsYXF1ZWxsZSAqKmxlcyBsaWduZXMgc29udCBkZXMgb2JzZXJ2YXRpb25zIGV0IGxlcyBjb2xvbm5lcyBzb250IGRlcyB2YXJpYWJsZXMqKi4gQWluc2ksIHVuIHRhYmxlYXUgZXN0IHVuZSBjb2xsZWN0aW9uIGRlIHZlY3RldXJzIGRlIG3Dqm1lIGxvbmd1ZXVyLCBjaGFxdWUgdmVjdGV1ciByZXByw6lzZW50YW50IHVuZSB2YXJpYWJsZS4gQ2hhcXVlIHZhcmlhYmxlIGVzdCBsaWJyZSBkZSBwcmVuZHJlIGxlIHR5cGUgZGUgZG9ubsOpZXMgYXBwcm9wcmnDqS4gTGEgcG9zaXRpb24gZCd1bmUgZG9ubsOpZSBkYW5zIGxlIHZlY3RldXIgY29ycmVzcG9uZCDDoCB1bmUgb2JzZXJ2YXRpb24uCgpJbWFnaW5leiBxdWUgdm91cyBjb25zaWduZXogZGVzIGRvbm7DqWVzIGRlIGRpZmbDqXJlbnRzIHNpdGVzIChBLCBCIGV0IEMpLCBldCBxdWUgY2hhcXVlIHNpdGUgcG9zc8OoZGUgc2VzIHByb3ByZXMgY2FyYWN0w6lyaXN0aXF1ZXMuIElsIGVzdCByZWRvbmRhbnQgZGUgZMOpY3JpcmUgbGUgc2l0ZSBwb3VyIGNoYXF1ZSBvYnNlcnZhdGlvbi4gVm91cyBwcsOpZsOpcmVyZXogY3LDqWVyIGRldXggdGFibGVhdXg6IHVuIHBvdXIgZMOpY3JpcmUgdm9zIG9ic2VydmF0aW9ucywgZXQgdW4gYXV0cmUgcG91ciBkw6ljcmlyZSBsZXMgc2l0ZXMuIERlIGNldHRlIG1hbmnDqHJlLCB2b3VzIGNyw6lleiB1bmUgY29sbGVjdGlvbiBkZSB0YWJsZWF1eCBpbnRlcmVsacOpczogdW5lICoqYmFzZSBkZSBkb25uw6llcyoqLiBSIHBldXQgc291dGlyZXIgZGVzIGRvbm7DqWVzIGRlcyBiYXNlcyBkZSBkb25uw6llcyBncsOiY2UgYXUgbW9kdWxlIERCSSwgcXVpIG4nZXN0IHBhcyBjb3V2ZXJ0IMOgIGNlIHN0YWRlIGRlIGTDqXZlbG9wcGVtZW50IGR1IGNvdXJzLgoKRGFucyBSLCBsZXMgZG9ubsOpZXMgc3RydWN0dXLDqWVzIGVuIHRhYmxlYXV4LCBhaW5zaSBxdWUgbGVzIG9ww6lyYXRpb25zIHN1ciBsZXMgdGFibGVhdXgsIHBldXZlbnQgw6p0cmUgZ8OpcsOpcyBncsOiY2UgYXV4IG1vZHVsZXMgcmVhZHIsIGRwbHlyIGV0IHRpZHlyLiBNYWlzIGF2YW50IGRlIHNlIGxhbmNlciBkYW5zIGwndXRpbGlzYXRpb24gZGUgY2VzIG1vZHVsZXMsIHZveW9ucyBxdWVscXVlcyByw6hnbGVzIMOgIHN1aXZyZSBwb3VyIGJpZW4gc3RydWN0dXJlciBzZXMgZG9ubsOpZXMgZW4gZm9ybWF0ICp0aWR5KiwgdW4gZHUgamFyZ29uIHRpZHl2ZXJzZSBxdWkgc2lnbmlmaWUgKnByb3ByZW1lbnQgb3JnYW5pc8OpKi4KCiMgT3JnYW5pc2VyIHVuIHRhYmxlYXUgZGUgZG9ubsOpZXMKCkFmaW4gZGUgcmVwcsOpcmVyIGNoYXF1ZSBjZWxsdWxlIGQndW4gdGFibGVhdSwgb24gYXR0cmlidWUgw6AgY2hhcXVlIGxpZ25lcyBldCDDoCBjaGFxdWUgY29sb25uZSBjb2xvbm5lcyB1biBpZGVudGlmaWFudCAqdW5pcXVlKiwgcXVlIGwnb24gbm9tbWUgKmluZGljZSogcG91ciBsZXMgbGlnbmVzIGV0ICplbnTDqnRlKiBwb3VyIGxlcyBjb2xvbm5lcy4KCj4gKipSw6hnbGUgbm8gMS4qKiBVbmUgdmFyaWFibGUgcGFyIGNvbG9ubmUsIHVuZSBvYnNlcnZhdGlvbiBwYXIgbGlnbmUsIHVuZSB2YWxldXIgcGFyIGNlbGx1bGUuCgpMZXMgdW5pdMOpcyBleHDDqXJpbWVudGFsZXMgc29udCBkw6ljcml0cyBwYXIgdW5lIG91IHBsdXNpZXVycyB2YXJpYWJsZXMgcGFyIGRlcyBjaGlmZnJlcyBvdSBkZXMgbGV0dHJlcy4gQ2hhcXVlIHZhcmlhYmxlIGRldnJhaXQgw6p0cmUgcHLDqXNlbnRlIGVuIHVuZSBzZXVsZSBjb2xvbm5lLCBldCBjaGFxdWUgbGlnbmUgZGV2cmFpdCBjb3JyZXNwb25kcmUgw6AgdW5lIHVuaXTDqSBleHDDqXJpbWVudGFsZSBvw7kgY2VzIHZhcmlhYmxlcyBvbnQgw6l0w6kgbWVzdXLDqWVzLiBMYSByw6hnbGUgcGFyYWl0IHNpbXBsZSwgbWFpcyBlbGxlIGVzdCByYXJlbWVudCByZXNwZWN0w6llLiBQcmVuZXogcGFyIGV4ZW1wbGUgbGUgdGFibGVhdSBzdWl2YW50LgoKfCBTaXRlIHwgVHJhaXRlbWVudCBBIHwgVHJhaXRlbWVudCBCIHwgVHJhaXRlbWVudCBDIHwKfCAtLS0gfCAtLS0gfCAtLS0gfCAtLS0gfAp8IFNhaW50ZS1aw6lwaGlyaW5lIHwgNC4xIHwgOC4yIHwgNi44IHwKfCBTYWludGUtQXVyw6lsaWUgfCA1LjggfCA1LjkgfCBOQSB8CnwgU2FpbnQtU2VyZ2Utw4l0aWVubmUgfCAyLjkgfCAzLjQgfCA0LjYgfAoKKlRhYmxlYXUgMS4gUmVuZGVtZW50cyBvYnRlbnVzIHN1ciBsZXMgc2l0ZXMgZXhww6lyaW1lbnRhdXggc2Vsb24gbGVzIHRyYWl0ZW1lbnRzLioKClF1J2VzdC1jZSBxdWkgY2xvY2hlIGF2ZWMgY2UgdGFibGVhdT8gQ2hhcXVlIGxpZ25lIGVzdCB1bmUgb2JzZXJ2YXRpb24sIG1haXMgY29udGllbnQgcGx1c3NpZXVycyBvYnNlcnZhdGlvbnMgZCd1bmUgbcOqbWUgdmFyaWFibGUsIGxlIHJlbmRlbWVudCwgcXVpIGRldmllbnQgw6l0YWzDqSBzdXIgcGx1c2lldXJzIGNvbG9ubmVzLiAqw4AgYmllbiB5IHBlbnNlciosIGxlIHR5cGUgZGUgdHJhaXRlbWVudCBlc3QgdW5lIHZhcmlhYmxlIGV0IGxlIHJlbmRlbWVudCBlbiBlc3QgdW5lIGF1dHJlOgoKfCBTaXRlIHwgVHJhaXRlbWVudCB8IFJlbmRlbWVudCB8CnwgLS0tIHwgLS0tIHwgLS0tIHwKfCBTYWludGUtWsOpcGhpcmluZSB8IEEgfCA0LjEgfAp8IFNhaW50ZS1aw6lwaGlyaW5lIHwgQiB8IDguMiB8CnwgU2FpbnRlLVrDqXBoaXJpbmUgfCBDIHwgNi44IHwKfCBTYWludGUtQXVyw6lsaWUgfCBBIHwgNS44IHwKfCBTYWludGUtQXVyw6lsaWUgfCBCIHwgNS45IHwKfCBTYWludGUtQXVyw6lsaWUgfCBDIHwgTkEgfAp8IFNhaW50LVNlcmdlLcOJdGllbm5lIHwgQSB8IDIuOSB8CnwgU2FpbnQtU2VyZ2Utw4l0aWVubmUgfCBCIHwgMy40IHwKfCBTYWludC1TZXJnZS3DiXRpZW5uZSB8IEMgfCA0LjYgfAoKKlRhYmxlYXUgMi4gUmVuZGVtZW50cyBvYnRlbnVzIHN1ciBsZXMgc2l0ZXMgZXhww6lyaW1lbnRhdXggc2Vsb24gbGVzIHRyYWl0ZW1lbnRzLioKClBsdXMgcHLDqWNpc8OpbWVudCwgbCdleHByZXNzaW9uICrDoCBiaWVuIHkgcGVuc2VyKiBzdWdnw6hyZSB1bmUgcsOpZmxleGlvbiBzdXIgbGEgc2lnbmlmaWNhdGlvbiBkZXMgZG9ubsOpZXMuIENlcnRhaW5lcyB2YXJpYWJsZXMgcGV1dmVudCBwYXJmb2lzIMOqdHJlIGludMOpZ3LDqWVzIGRhbnMgdW5lIG3Dqm1lIGNvbG9ubmUsIHBhcmZvaXMgcGFzLiBQYXIgZXhlbXBsZSwgbGVzIGNvbmNlbnRyYXRpb25zIGVuIGN1aXZyZSwgemluYyBldCBwbG9tYiBkYW5zIHVuIHNvbCBjb250YW1pbsOpIHBldXZlbnQgw6p0cmUgcGxhY8OpcyBkYW5zIGxhIG3Dqm1lIGNvbG9ubmUgIkNvbmNlbnRyYXRpb24iIG91IGTDqWNsaW7DqWVzIGVuIHBsdXNpZXVycyBjb2xvbm5lcyBDdSwgWm4gZXQgUGIuIExhIHByZW1pw6hyZSB2ZXJzaW9uIHRyb3V2ZXJhIHNvbiB1dGlsaXTDqSBwb3VyIGRlcyBjcsOpZXIgZGVzIGdyYXBoaXF1ZXMgKGNoYXBpdHJlIDMpLCBhbG9ycyBxdWUgbGEgZGV1eGnDqG1lIGZhdm9yaXNlIGxlIHRyYWl0ZW1lbnQgc3RhdGlzdGlxdWUgKGNoYXBpdHJlIDUpLiBJbCBlc3QgcG9zc2libGUgZGUgcGFzc2VyIGQndW4gZm9ybWF0IMOgIGwnYXV0cmUgZ3LDomNlIMOgIGxhIGZvbmN0aW9uIGBnYXRoZXIoKWAgZXQgYHNwcmVhZCgpYCBkdSBtb2R1bGUgdGlkeXIuCgo+ICoqUsOoZ2xlIG5vIDIuKiogVW4gdGFibGVhdSBwYXIgdW5pdMOpIG9ic2VydmF0aW9ubmVsbGU6IG5lIHBhcyByw6lww6l0ZXIgbGVzIGluZm9ybWF0aW9ucy4KClJlcnBlbm9ucyBsYSBtw6ptZSBleHDDqXJpZW5jZS4gU3VwcG9zb25zIHF1ZSB2b3VzIG1lc3VyZXogbGEgcHLDqWNpcGl0YXRpb24gw6AgbCfDqWNoZWxsZSBkdSBzaXRlLgoKfCBTaXRlIHwgVHJhaXRlbWVudCB8IFJlbmRlbWVudCB8IFByw6ljaXBpdGF0aW9ucyB8CnwgLS0tIHwgLS0tIHwgLS0tIHwgLS0tIHwKfCBTYWludGUtWsOpcGhpcmluZSB8IEEgfCA0LjEgfCA4MTMgfAp8IFNhaW50ZS1aw6lwaGlyaW5lIHwgQiB8IDguMiB8IDgxMyB8CnwgU2FpbnRlLVrDqXBoaXJpbmUgfCBDIHwgNi44IHwgODEzIHwKfCBTYWludGUtQXVyw6lsaWUgfCBBIHwgNS44IHwgNjQyIHwKfCBTYWludGUtQXVyw6lsaWUgfCBCIHwgNS45IHwgNjQyIHwKfCBTYWludGUtQXVyw6lsaWUgfCBDIHwgTkEgfCA2NDIgfAp8IFNhaW50LVNlcmdlLcOJdGllbm5lIHwgQSB8IDIuOSB8IDEwMjggfAp8IFNhaW50LVNlcmdlLcOJdGllbm5lIHwgQiB8IDMuNCB8IDEwMjggfAp8IFNhaW50LVNlcmdlLcOJdGllbm5lIHwgQyB8IDQuNiB8IDEwMjggfAoKKlRhYmxlYXUgMy4gUmVuZGVtZW50cyBvYnRlbnVzIHN1ciBsZXMgc2l0ZXMgZXhww6lyaW1lbnRhdXggc2Vsb24gbGVzIHRyYWl0ZW1lbnRzLioKClNlZ21lbnRlciBsJ2luZm9ybWF0aW9uIGVuIGRldXggdGFibGVhdXggc2VyYWl0IHByw6lmw6lyYWJsZS4KCnwgU2l0ZSB8IFByw6ljaXBpdGF0aW9ucyB8CnwgLS0tIHwgLS0tIHwKfCBTYWludGUtWsOpcGhpcmluZSB8IDgxMyB8CnwgU2FpbnRlLUF1csOpbGllIHwgNjQyIHwKfCBTYWludC1TZXJnZS3DiXRpZW5uZSB8IDEwMjggfAoKKlRhYmxlYXUgNC4gUHLDqWNpcGl0YXRpb25zIHN1ciBsZXMgc2l0ZXMgZXhww6lyaW1lbnRhdXguKgoKTGVzIHRhYmxlYXV4IDIgZXQgNCwgZW5zZW1ibGUsIGZvcm1lbnQgdW5lIGJhc2UgZGUgZG9ubsOpZXMgKGNvbGxlY3Rpb24gb3JnYW5pc8OpZSBkZSB0YWJsZWF1eCkuIExlcyBvcMOpcmF0aW9ucyBkZSBmdXNpb24gZW50cmUgbGVzIHRhYmxlYXV4IHBldXZlbnQgw6p0cmUgZWZmZWN0dcOpZXMgZ3LDomNlIGF1eCBmb25jdGlvbnMgZGUgam9pbnR1cmUgKGBsZWZ0X2pvaW4oKWAsIHBhciBleGVtcGxlKSBkdSBtb2R1bGUgdGlkeXIuCgo+ICoqUsOoZ2xlIG5vIDMuKiogTmUgcGFzIGJvdXNpbGxlciBsZXMgZG9ubsOpZXMuCgpQYXIgZXhlbXBsZS4KCi0gKkFqb3V0ZXIgZGVzIGNvbW1lbnRhaXJlcyBkYW5zIGRlcyBjZWxsdWxlcyouIFNpIHVuZSBjZWxsdWxlIG3DqXJpdGUgZCfDqnRyZSBjb21tZW50w6llLCBpbCBlc3QgcHLDqWbDqXJhYmxlIGRlIHBsYWNlciBsZXMgY29tbWVudGFpcmVzIHNvaXQgZGFucyB1biBmaWNoaWVyIGTDqWNyaXZhbnQgbGUgdGFibGVhdSBkZSBkb25uw6llcywgc29pdCBkYW5zIHVuZSBjb2xvbm5lIGRlIGNvbW1lbnRhaXJlIGp1c3RhcG9zw6llIMOgIGxhIGNvbG9ubmUgZGUgbGEgdmFyaWFibGUgw6AgY29tbWVudGVyLiBQYXIgZXhlbXBsZSwgc2kgdm91cyBuJ2F2ZXogcGFzIG1lc3VyZSBsZSBwSCBwb3VyIHVuZSBvYnNlcnZhdGlvbiwgbifDqWNyaXZleiBwYXMgIsOpY2hhbnRpbGxvbiBjb250YW1pbsOpIiBkYW5zIGxhIGNlbGx1bGUsIG1haXMgYW5ub3RlciBkYW5zIHVuIGZpY2hpZXIgZCdleHBsaWNhdGlvbiBxdWUgbCfDqWNoYW50aWxsb24gbm8gWCBhIMOpdMOpIGNvbnRhbWluw6kuIFNpIGxlcyBjb21tZW50YWlyZXMgc29udCBzeXN0w6ltYXRpcXVlLCBpbCBwZXV0IMOqdHJlIHByYXRpcXVlIGRlIGxlcyBpbnNjcmlyZSBkYW5zIHVuZSBjb2xvbm5lIGBjb21tZW50YWlyZV9wSGAuCi0gKkluc2NyaXRpb24gbm9uIHN5c3TDqW1hdGlxdWVzKi4gSWwgYXJyaXZlIHNvdXZlbnQgcXVlIGRlcyBjYXTDqWdvcmllcyBkJ3VuZSB2YXJpYWJsZSBvdSBxdWUgZGVzIHZhbGV1cnMgbWFucXVhbnRlcyBzb2llbnQgYW5ub3TDqWVzIGRpZmbDqXJlbW1lbnQuIElsIGFycml2ZSBtw6ptZSBxdWUgbGUgc8OpcGFyYXRldXIgZMOpY2ltYWwgc29pdCBub24gc3lzdMOpbWF0aXF1ZSwgcGFyZm9pcyBub3TDqSBwYXIgdW4gcG9pbnQsIHBhcmZvaXMgcGFyIHVuZSB2aXJndWxlLiBQYXIgZXhlbXBsZSwgdW5lIGZvaXMgaW1wb3J0w6lzIGRhbnMgdm90cmUgc2Vzc2lvbiwgbGVzIGNhdMOpZ29yaWVzIGBTdC1PdXJzYCBldCBgU2FpbnQtT3Vyc2Agc2Vyb250IHRyYWl0w6llcyBjb21tZSBkZXV4IGNhdMOpZ29yaWVzIGRpc3RpbmN0ZXMuIERlIG3Dqm1lLCBsZXMgY2VsbHVsZXMgY29ycmVzcG9uZGFudCDDoCBkZXMgdmFsZXVycyBtYW5xdWFudGVzIG5lIGRldnJhaWVudCBwYXMgw6p0cmUgaW5zY3JpdGUgcGFyZm9pcyBhdmVjIHVuZSBjZWxsdWxlIHZpZGUsIHBhcmZvaXMgYXZlYyB1biBwb2ludCwgcGFyZm9pcyBhdmVjIHVuIHRpcmV0IG91IGF2ZWMgbGEgbWVudGlvbiBgTkFgLiBMZSBwbHVzIHNpbXBsZSBlc3QgZGUgbGFpc3NlciBzeXN0w6ltYXRpcXVlbWVudCBjZXMgY2VsbHVsZXMgdmlkZXMuCi0gKkluY2x1cmUgZGVzIG5vdGVzIGRhbnMgdW4gdGFibGVhdSouIExhIHLDqGdsZSAidW5lIGNvbG9ubmUsIHVuZSB2YXJpYWJsZSIgbidlc3QgcGFzIHJlc3BlY3TDqWUgc2kgb24gYWpvdXRlIGRlcyBub3RlcyB1biBwZXUgbidpbXBvcnRlIG/DuSBzb3VzIG91IMOgIGPDtHTDqSBkdSB0YWJsZWF1LgotICpBam91dGVyIGRlcyBzb21tYWlyZXMqLiBTaSB2b3VzIGFqb3V0ZXogdW5lIGxpZ25lIHNvdXMgdW4gdGFibGVhdSBjb21wcmVuYW50IGxhIG1veWVubmUgZGUgY2hhcXVlIGNvbG9ubmUsIHF1J2VzdC1jZSBxdWkgYXJyaXZlcmEgbG9yc3F1ZSB2b3VzIGltcG9ydGVyZXogdm90cmUgdGFibGVhdSBkYW5zIHZvdHJlIHNlc3Npb24gZGUgdHJhdmFpbD8gTGEgbGlnbmUgc2VyYSBjb25zaWTDqXLDqWUgY29tbWUgdW5lIG9ic2VydmF0aW9uIHN1cHBsw6ltZW50YWlyZS4KLSAqSW5jbHVyZSB1bmUgaGnDqXJhcmNoaWUgZGFucyBsZSBlbnTDqnRlcyouIEFmaW4gZGUgY29uc2lnbmVyIGRlcyBkb25uw6llcyBkZSB0ZXh0dXJlIGR1IHNvbCwgY29tcHJlbmFudCBsYSBwcm9wcm90aW9uIGRlIHNhYmxlLCBkZSBsaW1vbiBldCBkJ2FyZ2lsZSwgdm91cyBvcmdhbmlzZXogdm90cmUgZW50w6p0ZSBlbiBwbHVzaWV1cnMgbGlnbmVzLiBVbmUgbGlnbmUgcG91ciBsYSBjYXTDqWdvcmllIGRlIGRvbm7DqWUsICpUZXh0dXJlKiwgZnVzaW9ubsOpZSBzdXIgdHJvaXMgY29sb25uZXMsIHB1aXMgdHJvaXMgY29sb25uZXMgaW50aXR1bGzDqWVzICpTYWJsZSosICpMaW1vbiogZXQgKkFyZ2lsZSouIFZvdHJlIHRhYmxlYXUgZXN0IGpvbGksIG1haXMgaWwgbmUgcG91cnJhIHBhcyDDqnRyZSBpbXBvcnTDqSBjb25mb3Jtw6ltZW50IGRhbnMgdW4gdm90cmUgc2Vzc2lvbiBkZSBjYWxjdWw6IG9uIHJlY2hlcmNoZSAqdW5lIGVudMOqdGUgdW5pcXVlIHBhciBjb2xvbm5lKi4gVm90cmUgdGFibGVhdSBkZSBkb25uw6llcyBkZXZyYWl0IHBsdXTDtHQgcG9ydGVyIGxlcyBlbnTDqnRlcyAqVGV4dHVyZSBzYWJsZSosICpUZXh0dXJlIGxpbW9uKiBldCAqVGV4dHVyZSBhcmdpbGUqLiBVbiBjb25zZWlsOiByw6lzZXJ2ZXIgbGUgdHJhdmFpbCBlc3Row6l0aXF1ZSDDoCBsYSB0b3V0ZSBmaW4gZCd1biBmbHV4IGRlIHRyYXZhaWwuCgojIEZvcm1hdHMgZGUgdGFibGVhdQoKUGx1c2lldXJzIG91dGlscyBzb250IMOgIHZvdHJlIGRpc3Bvc2l0aW9uIHBvdXIgY3LDqWVyIGRlcyB0YWJsZWF1eC4gSmUgdm91cyBwcsOpc2VudGUgaWNpIGxlcyBwbHVzIGNvbW11bnMuCgojIyAqeGxzKiBvdSAqeGxzeCoKTWljcm9zb2Z0IEV4Y2VsIGVzdCB1biBsb2dpY2llbCBkZSB0eXBlICp0YWJsZXVyKiwgb3UgY2hpZmZyaWVyIMOpbGVjdHJvbmlxdWUuIEwnYW5jaWVuIGZvcm1hdCAqeGxzKiBhIMOpdMOpIHJlbXBsYWPDqSBwYXIgbGUgZm9ybWF0ICp4bHN4KiBhdmVjIGwnYXJyaXbDqWUgZGUgTWljcm9zb2Z0IE9mZmljZSAyMDEwLiBJbCBzJ2FnaXQgZCd1biBmb3JtYXQgcHJvcHJpw6l0YWlyZSwgZG9udCBsJ2FsdGVybmF0aXZlIGxpYnJlIGxhIHBsdXMgY29ubnVlIGVzdCBsZSBmb3JtYXQgKm9kcyosIHBvcHVsYXJpc8OpIHBhciBsYSBzdWl0ZSBidXJlYXV0aXF1ZSBMaWJyZU9mZmljZS4gTGVzIGZvcm1hdHMgKnhscyosICp4bHN4KiBvdSAqb2RzKiBzb250IGRhdmFudGFnZSB1dGlsaXPDqXMgY29tbWUgb3V0aWxzIGRlIGNhbGN1bCBxdWUgZCdlbnRyZXBvc2FnZSBkZSBkb25uw6llcy4gSWxzIGNvbnRpZW5uZW50IGRlcyBmb3JtdWxlcywgZGVzIGdyYXBoaXF1ZXMsIGR1IGZvcm1hdHRhZ2UgZGUgY2VsbHVsZSwgZXRjLiAqSmUgbmUgbGVzIHJlY29tbWFuZGUgcGFzIHBvdXIgc3RvY2tlciBkZXMgZG9ubsOpZXMqLgoKIyMgKmNzdioKTGUgZm9ybWF0ICpjc3YqLCBwb3VyICpjb21tYSBzZXBhcmF0ZWQgdmFsdWVzKiwgZXN0IHVuIGZpY2hpZXIgdGV4dGUsIHF1ZSB2b3VzIHBvdXZleiBvdXZyaXIgYXZlYyBuJ2ltcG9ydGUgcXVlbCDDqWRpdGV1ciBkZSB0ZXh0ZSBicnV0IChCbG9jIG5vdGUsIFtBdG9tXShodHRwczovL2F0b20uaW8pLCBbTm90ZXBhZCsrXShodHRwczovL25vdGVwYWQtcGx1cy1wbHVzLm9yZyksIGV0Yy4pLiBDaGFxdWUgY29sb25uZSBkb2l0IMOqdHJlIGTDqWxpbWl0w6llIHBhciB1biBjYXJhY3TDqHJlIGNvaMOpcmVudCAoY29udmVudGlvbm5lbGxlbWVudCB1bmUgdmlyZ3VsZSwgbWFpcyBlbiBmcmFuw6dhaXMgdW4gcG9pbnQtdmlyZ3VsZSBvdSB1bmUgdGFidWxhdGlvbiBwb3VyIMOpdml0ZXIgbGEgY29uZnVzaW9uIGF2ZWMgbGUgc8OpcGFyYXRldXIgZMOpY2ltYWwpIGV0IGNoYXF1ZSBsaWduZSBkdSB0YWJsZWF1IGVzdCB1biByZXRvdXIgZGUgbGlnbmUuIElsIGVzdCBwb3NzaWJsZSBkJ291dnJpciBldCBkJ8OpZGl0ZXIgbGVzIGZpY2hpZXJzIGNzdiBkYW5zIHVuIMOpZGl0ZXVyIHRleHRlLCBtYWlzIGlsIGVzdCBwbHVzIHByYXRpcXVlIGRlIGxlcyBvdXZyaXIgYXZlYyBkZXMgdGFibGV1cnMgKExpYnJlT2ZmaWNlIENhbGMsIE1pY3Jvc29mdCBFeGNlbCwgR29vZ2xlIFNoZWV0cywgZXRjLikuCgoqKkVuY29kYWdlIGRlcyBmaWNoaWVycyB0ZXh0ZSoqLiBQdWlzcXVlIGxlIGZvcm1hdCAqY3N2KiBlc3QgdW4gZmljaGllciB0ZXh0ZSwgdW4gc291Y2kgcGFydGljdWxpZXIgZG9pdCDDqnRyZSBwb3J0w6kgc3VyIGxhIG1hbmnDqHJlIGRvbnQgbGUgdGV4dGUgZXN0IGVuY29kw6kuIExlcyBjYXJhY3TDqHJlcyBhY2NlbnR1w6lzIHBvdXJyYWl0IMOqdHJlIGltcG9ydGVyIGluY29ycmVjdGVtZW50IHNpIHZvdXMgaW1wb3J0ZXogdm90cmUgdGFibGVhdSBlbiBzcMOpY2lmaWFudCBsZSBtYXV2YWlzIGVuY29kYWdlLiBQb3VyIGxlcyBmaWNoaWVycyBlbiBsYW5ndWVzIG9jY2lkZW50YWxlcywgbCdlbmNvZGFnZSBVVEYtOCBkZXZyYWl0IMOqdHJlIHV0aWxpc8OpLiBUb3V0ZWZvaXMsIHBhciBkw6lmYXV0LCBFeGNlbCB1dGlsaXNlIHVuIGVuY29kYWdlIGRlIE1pY3Jvc29mdC4gU2kgbGUgKmNzdiogYSDDqXTDqSBnw6luw6lyw6kgcGFyIEV4Y2VsLCBpbCBlc3QgcHLDqWbDqXJhYmxlIGRlIGwnb3V2cmlyIGF2ZWMgdm90cmUgw6lkaXRldXIgdGV4dGUgZXQgZGUgbCdlbnJlZ2lzdHJlciBkYW5zIGwnZW5jb2RhZ2UgVVRGLTguCgojIyAqanNvbioKQ29tbWUgbGUgZm9ybWF0ICpjc3YqLCBsZSBmb3JtYXQgKmpzb24qIGluZGlxdWUgdW4gZmljaGllciBlbiB0ZXh0ZSBjbGFpci4gSWwgZXN0IHV0aWxpc8OpIGRhdmFudGFnZSBwb3VyIGxlIHBhcnRhZ2UgZGUgZG9ubsOpZXMgZGVzIGFwcGxpY2F0aW9ucyB3ZWIuIEVuIGFuYWx5c2UgZXQgbW9kw6lsaXNhdGlvbiwgY2UgZm9ybWF0IGVzdCBzdXJ0b3V0IHV0aWxpc8OpIHBvdXIgbGVzIGRvbm7DqWVzIGfDqW9yw6lmw6lyZW5jw6llcy4gTCdlbmNvZGFnZSBlc3QgZ8OpcsOpIGRlIGxhIG3Dqm1lIG1hbmnDqHJlIHF1J3VuIGZpY2hpZXIgKmNzdiouCgojIyBTUUxpdGUKU1FMaXRlIGVzdCB1bmUgYXBwbGljYXRpb24gcG91ciBsZXMgYmFzZXMgZGUgZG9ubsOpZXMgcmVsYXRpb25uZWxsZXMgZGUgdHlwZSBTUUwgcXVpIG4nYSBwYXMgYmVzb2luIGRlIHNlcnZldXIgcG91ciBmb25jdGlvbm5lci4gTGVzIGJhc2VzIGRlIGRvbm5uw6llcyBTUUxpdGUgc29udCBlbmNvZMOpcyBkYW5zIGRlcyBmaWNoaWVycyBwb3J0YW50IGwnZXh0ZW5zaW9uICpkYiosIHF1aSBwZXV2ZW50IMOqdHJlIGZhY2lsZW1lbnQgcGFydGFnw6lzLgoKIyMgU3VnZ2VzdGlvbgpFbiAqY3N2KiBwb3VyIGxlcyBwZXRpdHMgdGFibGVhdXgsIGVuICpzcWxpdGUqIHBvdXIgbGVzIGJhc2VzIGRlIGRvbm7DqWVzIHBsdXMgY29tcGxleGVzLiBDZSBjb3VycyBzZSBjb25jZW50cmUgdG91dGVmb2lzIHN1ciBsZXMgZG9ubsOpZXMgZGUgdHlwZSAqY3N2Ki4KCiMgRW50cmVwb3NlciBzZXMgZG9ubsOpZXMKCkxhIG1hbmnDqHJlIGxhIHBsdXMgc8OpY3VyZSBwb3VyIGVudHJlcG9zZXIgc2VzIGRvbm7DqWVzIGVzdCBkZSBsZXMgY29uZmluZXIgZGFucyB1bmUgYmFzZSBkZSBkb25uw6llcyBzw6ljdXJpc8OpZSBzdXIgdW4gc2VydmV1ciBzw6ljdXJpc8OpIGRhbnMgdW4gZW52aXJvbm5lbWVudCBzw6ljdXJpc8OpIGV0IGQnZW5jcnlwdGVyIGxlcyBjb21tdW5pY2F0aW9ucy4gQydlc3QgYXVzc2kgbGEgbWFuacOocmUgbGEgbW9pbnMgYWNjZXNzaWJsZS4gRGVzIGVzcGFjZXMgZGUgc3RvY2thZ2UgbnVhZ2lxdWVzLCBjb21tZSBEcm9wYm94IG91IGQnYXV0cmVzIFtvcHRpb25zIHNpbWlsYWlyZXNdKGh0dHBzOi8vYWx0ZXJuYXRpdmV0by5uZXQvc29mdHdhcmUvZHJvcGJveC8pIChqZSBzdWdnw6hyZSBbU3BpZGVyT2FrXShodHRwczovL3NwaWRlcm9hay5jb20vb25lLyksIHF1aSBlc3Qgc8OpY3VyaXPDqSksIHBldXZlbnQgw6p0cmUgcHJhdGlxdWVzIHBvdXIgbGVzIGJhY2t1cHMgZXQgbGUgcGFydGFnZSBkZXMgZG9ubsOpZXMgYXZlYyB1bmUgw6lxdWlwZSBkZSB0cmF2YWlsIChxdWkgcmlzcXVlIGVuIHJldG91ciBkZSBib3VzaWxsZXIgdm9zIGRvbm7DqWVzKS4gTGUgc3VpdmkgZGUgdmVyc2lvbiBlc3QgcG9zc2libGUgY2hleiBjZXJ0YWlucyBmb3Vybmlzc2V1cnMgZCdlc3BhY2UgZGUgc3RvY2thZ2UuIE1haXMgcG91ciB1biBzdWl2aSBkZSB2ZXJzaW9uIHBsdXMgcmlnb3VyZXV4LCBsZXMgZXNwYWNlcyBkZSBkw6l2ZWxvcHBlbWVudCAoY29tbWUgR2l0SHViIGV0IEdpdExhYikgc29udCBwbHVzIGFwcHJvcHJpw6lzLiBEYW5zIHRvdXMgbGVzIGNhcywgaWwgZXN0IGltcG9ydGFudCBkZSBnYXJkZXIgKDEpIGRlcyBjb3BpZXMgYW5jaWVubmVzIHBvdXIgeSByZXZlbmlyIGVuIGNhcyBkJ2VycmV1cnMgZXQgKDIpIHVuIHBldGl0IGZpY2hpZXIgZMOpY3JpdmFudCBsZXMgY2hhbmdlbWVudHMgZWZmZWN0dcOpcyBzdXIgbGVzIGRvbm7DqWVzLgoKIyBNYW5pcHVsZXIgZGVzIGRvbm7DqWVzIGVuIG1vZGUgdGlkeXZlcnNlCgpMZSBtw6l0YS1tb2R1bGUgdGlkeXZlcnNlIHJlZ3JvdXBlIHVuZSBjb2xsZWN0aW9uIGRlIHByw6ljaWV1eCBtb2R1bGVzICBwb3VyIGwnYW5hbHlzZSBkZSBkb25uw6llcyBlbiBSLiBJbCBwZXJtZXQgZCdpbXBvcnRlciBkZXMgZG9ubsOpZXMgZGFucyB2b3RyZSBzZXNzaW9uIGRlIHRyYXZhaWwgYXZlYyByZWFkciwgZGUgbGVzIGV4cGxvcmVyIGF2ZWMgZ2dwbG90MiwgZGUgbGVzIHRyYW5zZm9ybWVyIGF2ZWMgdGlkeXIgZXQgZHBseXIgZXQgZGUgbGVzIGV4cG9ydGVyIGFldmMgcmVhZHIuIExlcyB0YWJsZWF1eCBkZSBjbGFzc2UgKmRhdGEuZnJhbWUqLCBjb21tZSBjZXV4IGRlIGxhIHBsdXMgbW9kZXJuZSBjbGFzc2UgKnRpYmJsZSosIHBldXZlbnQgw6p0cmUgbWFuaXB1bMOpcyDDoCB0cmF2ZXJzIGxlIGZsdXggZGUgdHJhdmFpbCBwb3VyIGwnYW5hbHlzZSBldCBsYSBtb2TDqWxpc2F0aW9uIChjaGFwaXRyZXMgc3VpdmFudHMpLiBDb21tZSBjJ8OpdGFpdCBsZSBjYXMgcG91ciBsZSBjaGFwaXRyZSBzdXIgbGEgdmlzdWFsaXNhdGlvbiwgY2UgY2hhcGl0cmUgZXN0IGxvaW4gZGUgY291dnJpciBsZXMgbm9tYnJldXNlcyBmb25jdGlvbm5hbGl0w6lzIHF1aSBzb250IG9mZmVydGVzIGRhbnMgbGUgdGlkeXZlcnNlLgoKIyMgSW1wb3J0ZXIgdm9zIGRvbm7DqWVzIGRhbnMgdm90ZXIgc2Vzc2lvbiBkZSB0cmF2YWlsCgpTdXBwb3NvbnMgcXVlIHZvdXMgYXZlYyBiaWVuIG9yZ2FuaXPDqSB2b3MgZG9ubsOpZXMgZW4gbW9kZSAqdGlkeSouIFBvdXIgbGVzIGltcG9ydGVyIGRhbnMgdm90cmUgc2Vzc2lvbiBldCBjb21tZW5jZXIgw6AgbGVzIGluc3BlY3Rlciwgdm91cyBsYW5jZXJleiB1bmUgZGVzIGNvbW1hbmRlcyBkdSBtb2R1bGUgcmVhZHIsIGTDqWNyaXRlcyBkYW5zIGxhIGRvY3VtZW50YXRpb24gZMOpZGnDqWUuCgotIGAgcmVhZF9jc3YoKWAgc2kgbGUgc8OpcGFyYXRldXIgZGUgY29sb25uZSBlc3QgdW5lIHZpcmd1bGUKLSBgIHJlYWRfY3N2MigpYCBzaSBsZSBzw6lwYXJhdGV1ciBkZSBjb2xvbm5lIGVzdCB1biBwb2ludC12aXJndWxlIGV0IHF1ZSBsZSBzw6lwYXJhdGV1ciBkw6ljaW1hbCBlc3QgdW5lIHZpcmd1bGUKLSBgIHJlYWRfdHN2KClgIHNpIGxlIHPDqXBhcmF0ZXVyIGRlIGNvbG9ubmUgZXN0IHVuZSB0YWJ1bGF0aW9uCi0gYCByZWFkX3RhYmxlKClgIHNpIGxlIHPDqXBhcmF0ZXVyIGRlIGNvbG9ubmUgZXN0IHVuIGVzcGFjZSBibGFuYwotIGAgcmVhZF9kZWxpbSgpYCBzaSBsZSBzw6lwYXJhdGV1ciBkZSBjb2xvbm5lIGVzdCB1biBhdXRyZSBjYXJhY3TDqHJlIChjb21tZSBsZSBwb2ludC12aXJndWxlKSBxdWUgdm91cyBzcMOpY2lmaWVyZXogZGFucyBsJ2FyZ3VtZW50IGBkZWxpbSA9ICI7ImAKCkxlcyBwcmluY2lwYXV4IGFyZ3VtZW50cyBzb250IGxlcyBzdWl2YW50cy4KCi0gYGZpbGVgOiBsZSBjaGVtaW4gdmVycyBsZSBmaWNoaWVyLiBDZSBjaGVtaW4gcGV1dCBhdXNzaSBiaWVuIMOqdHJlIHVuZSBhZHJlc3NlIGxvY2FsZSAoZGF0YS8uLi4pIHF1J3VuZSBhZHJlc3NlIGludGVybmV0IChodHRwczovLy4uLikuCi0gYGRlbGltYDogbGUgc3ltYm9sZSBkw6lsaW1pdGFudCBsZXMgY29sb25uZXMgZGFucyBsZSBjYXMgZGUgYHJlYWRfZGVsaW1gLgotIGBjb2xfbmFtZXNgOiBzaSBUUlVFLCBsYSBwcmVtacOocmUgbGlnbmUgZXN0IGwnZW50w6p0ZSBkdSB0YWJsZWF1LCBzaW5vbiBGQUxTRS4gU2kgdm91cyBzcMOpY2lmaWV6IHVuIHZlY3RldXIgbnVtw6lyaXF1ZSwgY2Ugc29udCBsZXMgbnVtw6lyb3MgZGVzIGxpZ25lcyB1dGlsaXPDqWVzIHBvdXIgbGUgbm9tIGRlIGwnZW50w6p0ZS4gU2kgdm91cyB1dGlsaXNleiB1biB2ZWN0ZXVyIGRlIGNoYXJhY3TDqHJlcywgY2Ugc29udCBsZXMgbm9tcyBkZXMgY29sb25uZXMgcXVlIHZvdXMgZMOpc2lyZXogZG9ubmVyIMOgIHZvdHJlIHRhYmxlYXUuCi0gYG5hYDogbGUgc3ltYm9sZSBzcMOpY2lmaWFudCB1bmUgdmFsZXVyIG1hbnF1YW50ZS4gTCdhcmd1bWVudCBgbmE9JydgIHNpZ25pZmllIHF1ZSBsZXMgY2VsbHVsZXMgdmlkZXMgc29udCBkZXMgZG9ubsOpZXMgbWFucXVhbnRlcy4gU2kgbGVzIHZhbGV1cnMgbWFucXVhbnRlcyBuZSBzb250IHBhcyB1bmlmb3JtZXMsIHZvdXMgcG91dmV6IGxlcyBpbmRpcXVlciBkYW5zIHVuIHZlY3RldXIsIHBhciBleGVtcGxlIGBuYSA9IGMoIiIsICJOQSIsICJOYU4iLCAiLiIsICItIilgLgotIGBsb2NhbGA6IGNldCBhcmd1bWVudCBwcmVuZCB1bmUgZm9uY3Rpb24gYGxvY2FsKClgIHF1aSBwZXV0IGluY2x1cmUgZGVzIGFyZ3VtZW50cyBkZSBmb3JtYXQgZGUgdGVtcHMsIG1haXMgYXVzc2kgZCdlbmNvZGFnZSAoW3ZvaXIgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9yZWFkci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9sb2NhbGUuaHRtbCkpCgpEJ2F1dHJlcyBhcmd1bWVudHMgcGV1dmVudCDDqnRyZSBzcMOpY2lmacOpcyBhdSBiZXNvaW4sIGV0IGxlcyByw6lww6l0ZXIgaWNpIGR1cGxpcXVlcmFpdCBsJ2luZm9ybWF0aW9uIGRlIGxhIGRvY3VtZW50YXRpb24gZGUgW2xhIGZvbmN0aW9uIGByZWFkX2NzdmAgZGUgcmVhZHJdKGh0dHBzOi8vcmVhZHIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvcmVhZF9kZWxpbS5odG1sKS4KCkplIGTDqWNvbnNlaWxsZSBkJ2ltcG9ydGVyIGRlcyBkb25uw6llcyBlbiBmb3JtYXQgeGxzIG91IHhsc3guIFNpIHRvdXRlZm9pcyBjZWxhIHZvdXMgY29udmllbnQsIGplIHZvdXMgcsOpZsOocmUgYXUgbW9kdWxlIFtyZWFkeGxdKGh0dHBzOi8vcmVhZHhsLnRpZHl2ZXJzZS5vcmcvKS4KCkwnW2FpZGUtbcOpbW9pcmUgZGUgcmVhZHJdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvZGF0YS1pbXBvcnQucGRmKSBlc3Qgw6AgYWZmaWNoZXIgcHLDqHMgZGUgc29pLgoKWyFbXShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMS9kYXRhLWltcG9ydC1jaGVhdHNoZWV0LTEtNjAweDQ2NC5wbmcpXShodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9yYXcvbWFzdGVyL2RhdGEtaW1wb3J0LnBkZikKQWlkZS1tw6ltb2lyZSBkZSByZWFkciwgc291cmNlOiBodHRwczovL3d3dy5yc3R1ZGlvLmNvbS9yZXNvdXJjZXMvY2hlYXRzaGVldHMvCgpOb3VzIGFsbG9ucyBjaGFyZ2VyIGRlcyBkb25uw6llcyBkZSBjdWx0dXJlIGRlIGxhIGNoaWNvdXTDqSAoKlJ1YnVzIGNoYW1hZW1vcnVzKiksIHVuIHBldGl0IGZydWl0IG5vcmRpcXVlLCB0aXLDqSBkZSBQYXJlbnQgZXQgYWwuICgyMDEzKS4gT3V2cm9ucyBkJ2Fib3JkIGxlIGZpY2hpZXIgcG91ciB2w6lyaWZpZXIgbGVzIHPDqXBhcmF0ZXVycyBkZSBjb2xvbm5lIGV0IGRlIGTDqWNpbWFsZS4KCiFbXShpbWFnZXMvMDRfY2hpY291dGUtY3N2LWF0b20ucG5nKQoKTGUgc8OpcGFyYXRldXIgZGUgY29sb25uZSBlc3QgdW4gcG9pbnQtdmlyZ3VsZSBldCBsZSBkw6ljaW1hbCBlc3QgdW5lIHZpcmd1bGUuCgpBdmVjIFtBdG9tXShodHRwczovL2F0b20uaW8vKSwgbW9uIMOpZGl0ZXVyIHRleHRlIHByw6lmw6lyw6ksIGplIHZhaXMgZGFucyBFZGl0ID4gU2VsZWN0IEVuY29kaW5nIGV0IGonb2J0aWVucyBiaWVuIGxlIFVURi04LgoKIVtdKGltYWdlcy8wNF9jaGljb3V0ZS1jc3YtZW5jb2RpbmcucG5nKQoKTm91cyBhbGxvbnMgZG9uYyB1dGlsaXNlciBgcmVhZF9jc3YyKClgIGF2ZWMgc2VzIGFyZ3VtZW50cyBwYXIgZMOpZmF1dC4KCmBgYHtyfQpsaWJyYXJ5KCJ0aWR5dmVyc2UiKQpjaGljb3V0ZSA8LSByZWFkX2NzdjIoJ2RhdGEvY2hpY291dGUuY3N2JykKYGBgCgpRdWVscXVlcyBjb21tYW5kZXMgdXRpbGVzIGluc3BlY3RlciBsZSB0YWJsZWF1OgoKLSBgaGVhZCgpYCBwcsOpc2VudGUgbCdlbnTDqnRlIGR1IHRhYmxlYXUsIHNvaXQgc2VzIDYgcHJlbWnDqHJlcyBsaWduZXMKLSBgc3RyKClgIGV0IGBnbGltcHNlKClgIHByw6lzZW50ZW50IGxlcyB2YXJpYWJsZXMgZHUgdGFibGVhdSBldCBsZXVyIHR5cGUgLSBgZ2xpbXBzZSgpYGVzdCBsYSBmb25jdGlvbiB0aWR5dmVyc2UgZXQgYHN0cigpYCBlc3QgbGEgZm9uY3Rpb24gY2xhc3NpcXVlIChqZSBwcsOpZsOocmUgYHN0cigpYCkKLSBgc3VtbWFyeSgpYCBwcsOpc2VudGUgZGVzIHN0YXRpc3RpcXVlcyBkZSBiYXNlIGR1IHRhYmxlYXUKLSBgbmFtZXMoKWAgb3UgYGNvbG5hbWVzKClgIHNvcnQgbGVzIG5vbXMgZGVzIGNvbG9ubmVzIHNvdXMgZm9ybWUgZCd1biB2ZWN0ZXVyCi0gYGRpbSgpYCBkb25uZSBsZXMgZGltZW5zaW9ucyBkdSB0YWJsZWF1LCBgbmNvbCgpYCBzb24gbm9tYnJlIGRlIGNvbG9ubmVzIGV0IGBucm93KClgIHNvbiBub21icmUgZGUgbGlnbmVzCi0gYHNraW1gIGVzdCB1bmUgZm9uY3Rpb24gZHUgbW9kdWxlIHNraW1yIG1vbnRyYW50IHVuIHBvcnRyYWl0IGdyYXBoaXF1ZSBldCBudW3DqXJpcXVlIGR1IHRhYmxlYXUKCioqRXh0cmEgMSoqLiBQbHVzaWV1cnMgbW9kdWxlcyBuZSBzZSB0cm91dmVudCBwYXMgZGFucyBsZXMgZMOpcMO0dCBDUkFOLCBtYWlzIHNvbnQgZGlzcG9uaWJsZXMgc3VyIEdpdEh1Yi4gUG91ciBsZXMgaW5zdGFsbGVyLCBpbnN0YWxsZXogZCdhYm9yZCBsZSBtb2R1bGUgZGV2dG9vbHMgZGlzcG9uaWJsZSBzdXIgQ1JBTi4gVm91cyBwb3VycmV6IGFsb3JzIGluc3RhbGxlciBsZXMgcGFja2FnZXMgZGUgR2l0SHViIGNvbW1lIG9uIGxlIGZhaXQgYXZlYyBsZSBwYWNrYWdlIHNraW1yLgoKKipFeHRyYSAyKiouIExvcnNxdWUgamUgZMOpc2lyZSB1dGlsaXNlciB1bmUgZm9uY3Rpb24sIG1haXMgc2FucyBjaGFyZ2VyIGxlIG1vZHVsZSBkYW5zIGxhIHNlc3Npb24sIGondXRpbGlzZSBsYSBub3RhdGlvbiBgbW9kdWxlOjpmb25jdGlvbmAuIENvbW1lIGRhbnMgY2UgY2FzLCBwb3VyIHNraW1yLgoKYGBge3J9CiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJyb3BlbnNjaWxhYnMvc2tpbXIiKQpza2ltcjo6c2tpbShjaGljb3V0ZSkKYGBgCgoqKkV4ZXJjaWNlKiouIEluc3BlY3RleiBsZSB0YWJsZWF1LgoKIyMgQ29tbWVudCBzw6lsZWN0aW9ubmVyIGV0IGZpbHRyZXIgZGVzIGRvbm7DqWVzPwoKT24gdXRpbGlzZXIgbGUgdGVybWUgKnPDqWxlY3Rpb25uZXIqIGxvcnNxdWUgbCdvbiBkw6lzaXJlIGNob2lzaXIgdW5lIG91IHBsdXNpZXVycyBsaWduZXMgZXQgY29sb25uZXMgZCd1biB0YWJsZWF1IChsYSBwbHVwYXJ0IGR1IHRlbXBzIGRlcyBjb2xvbm5lcykuIEwnYWN0aW9uIGRlICpmaWx0cmVyKiBzaWduaWZpZSBkZSBzw6lsZWN0aW9ubmVyIGRlcyBsaWduZXMgc2Vsb24gY2VydGFpbnMgY3JpdMOocmVzLgoKIyMjIFPDqWxlY3Rpb25uZXIKClZvaWNpIHRyb2lzIG1hbmnDqHJlcyBkZSBzw6lsZWN0aW9ubmVyIHVuZSBjb2xvbm5lIGVuIFIuCgotIFVuZSBtw6l0aG9kZSByYXBpZGUgbWFpcyBwZXUgZXhwcmVzc2l2ZSBjb25zaXN0ZSDDoCBpbmRpcXVlciBsZXMgdmFsZXVycyBudW3DqXJpcXVlcyBkZSBsJ2luZGljZSBkZSBsYSBjb2xvbm5lIGVudHJlIGRlcyBjcm9jaGV0cy4gSWwgcydhZ2l0IGQnYXBwZWxlciBsZSB0YWJsZWF1IHN1aXZpdCBkZSBjcm9jaGV0cy4gTCdpbnTDqXJpZXVyIGRlcyBjcm9jaGV0cyBjb21wcmVuZCBkZXV4IMOpbMOpbWVudHMgc8OpcGFyw6lzIHBhciB1bmUgdmlyZ3VsZS4gTGUgcHJlbWllciDDqWzDqW1lbnQgc2VydCDDoCBmaWx0ZXIgc2Vsb24gbCdpbmRpY2UsIGxlIGRldXhpw6htZSBzZXJ0IMOgIHPDqWxlY3Rpb25uZXIgc2Vsb24gbCdpbmRpY2UuIEFpbnNpOgotIGBjaGljb3V0ZVssIDFdYDogc8OpbGVjdGlvbm5lciBsYSBwcmVtacOocmUgY29sb25uZQotIGBjaGljb3V0ZVssIDE6MTBdYDogc8OpbGVjdGlvbm5lciBsZXMgMTAgcHJlbWnDqHJlcyBjb2xvbm5lcwotIGBjaGljb3V0ZVssIGMoMiwgNCwgNSldYDogc8OpbGVjdGlvbm5lciBsZXMgY29sb25uZXMgMiwgNCBldCA1Ci0gYGNoaWNvdXRlW2MoMTAsIDEzLCAyMCksIGMoMiwgNCwgNSldYDogc8OpbGVjdGlvbm5lciBsZXMgY29sb25uZXMgMiwgNCBldCA1IGV0IGxlcyBsaWduZXMgMTAsIDEzIGV0IDIwLgoKLSBVbmUgYXV0cmUgbcOpdGhvZGUgcmFwaWRlLCBtYWlzIHBsdXMgZXhwcmVzc2l2ZSwgY29uc2lzdGUgw6AgYXBwZWxlciBsZSB0YWJsZWF1LCBzdWl2aSBkdSBzeW1ib2xlIGAkYCwgcHVpcyBsZSBub20gZGUgbGEgY29sb25uZS4KCj4gKipUcnVjKiouIExhIHBsdXBhcnQgZGVzIElERSwgY29tbWUgUlN0dWRpbywgcGV1dmVudCB2b3VzIHByb3Bvc2VyIGRlcyBjb2xvbm5lcyBkYW5zIHVuZSBsaXN0ZS4gQXByw6hzIGF2b2lyIGVudHJlciBsZSBgJGAsIHRhcGVyIHN1ciBsYSB0b3VjaGUgZGUgdGFidWxhdGlvbjogdm91cyBwb3VycmV6IHPDqWxlY3Rpb25uZXIgbGEgY29sb25uZSBkYW5zIHVuZSBsaXN0ZSBkw6lmaWxhbnRlLgoKIVtdKGltYWdlcy8wNF9hdXRvLWNvbXBsZXRlLWNvbHMucG5nKQoKLSBVbmUgYXV0cmUgb3B0aW9uIGVzdCBkJ2luc2NyaXJlIGxlIG5vbSBkZSBsYSBjb2xvbm5lLCBvdSBkdSB2ZWN0ZXVyIGRlcyBjb2xvbm5lcywgZW50cmUgZGVzIGNyb2NoZXRzIHN1aXZhbnQgbGUgbm9tIGR1IHRhYmxlYXUsIGMnZXN0LcOgLWRpcmUgYGNoaWNvdXRlW2MoIlNpdGUiLCAiTGF0aXR1ZGVfbSIsICJMb25naXR1ZGVfbSIpXWAuCgotIEVuZmluLCBkYW5zIHVuZSBzw6lxdWVuY2UgZCdvcMOpcmF0aW9ucyBlbiBtb2RlIHBpcGVsaW5lIChjaGFxdWUgb3DDqXJhdGlvbiBlc3QgbWlzZSDDoCBsYSBzdWl0ZSBkZSBsYSBwcsOpY8OpZGVudGUgZW4gcGxhw6dhbnQgbGUgKnBpcGUqIGAlPiVgIGVudHJlIGNoYWN1bmUpLCBpbCBwZXV0IMOqdHJlIHByw6lmw6lyYWJsZSBkZSBzw6lsZWN0aW9ubmVyIGRlcyBjb2xvbm5lcyBhdmVjIGxhIGZvbmN0aW9uIGBzZWxlY3QoKWAsIGkuZS4KCmBgYApjaGljb3V0ZSAlPiUKc2VsZWN0KFNpdGUsIExhdGl0dWRlX20sIExvbmdpdHVkZV9tKQpgYGAKCkxhIGZvbmN0aW9uIGBzZWxlY3QoKWAgcGVybWV0IGF1c3NpIGRlIHRyYXZhaWxsZXIgZW4gZXhjbHVzaW9uLiBBaW5zaSBwb3VyIGVubGV2ZXIgZGVzIGNvbG9ubmVzLCBvbiBwbGFjZXJhIHVuIGAtYCAoc2lnbmUgZGUgc291c3RyYWN0aW9uKSBkZXZhbnQgbGUgbm9tIGRlIGxhIGNvbG9ubmUuCgpEJ2F1dHJlIGFyZ3VtZW50cyBkZSBgc2VsZWN0KClgIHBlcm1ldHRlbnQgdW5lIHPDqWxlY3Rpb24gcmFwaWRlLiBQYXIgZXhlbXBsZSwgcG91ciBvYnRlbmlyIGxlcyBjb2xvbm5lcyBjb250ZW5hbnQgZGVzIHBvdXJjZW50YWdlczoKCmBgYHtyfQpjaGljb3V0ZSAlPiUKICBzZWxlY3QoZW5kc193aXRoKCJwb3VyYyIpKSAlPiUKICBoZWFkKDMpCmBgYAoKIyMjIEZpbHRyZXIKCkNvbW1lIGMnZXN0IGxlIGNhcyBkZSBsYSBzw6lsZWN0aW9uLCBvbiBwb3VycmEgZmlsdHJlciB1biB0YWJsZWF1IGRlIHBsdXNpZXVycyBtYW5pw6hyZXMuIEonYWkgZMOpasOgIHByw6lzZW50w6kgY29tbWVudCBmaWx0cmVyIHNlbG9uIGxlcyBpbmRpY2VzIGRlcyBsaWduZXMuIExlcyBhdXRyZXMgbWFuacOocmVzIHJlcG9zZW50IG7DqWFubW9pbnMgc3VyIHVuZSBvcMOpcmF0aW9uIGxvZ2lxdWUgYD09YCwgYDxgLCBgPmAgb3UgYCVpbiVgIChsZSAlaW4lIHNpZ25pZmllICpzZSB0cm91dmUgcGFybWkqIGV0IHBldXQgw6p0cmUgc3VpdmkgZCd1biB2ZWN0ZXVyIGRlIHZhbGV1ciBxdWUgbCdvbiBkw6lzaXJlIGFjY2VwdGVyKS4KCkxlcyBjb25kaXRpb25zIGJvb2zDqWVubmVzIHBldXZlbnQgw6p0cmUgY29tYmluw6llcyBhdmVjIGxlcyBvcMOpcmF0ZXVycyAqZXQqLCAgYCZgLCBldCAqb3UqLCBgfGAuIFBvdXIgcmFwcGVsLAoKCnwgT3DDqXJhdGlvbiB8IFLDqXN1bHRhdCB8CnwgLS0tLS0tLS0tIHwgLS0tLS0tLS0gfAp8IFZyYWkgKipldCoqIFZyYWkgfCBWcmFpIHwKfCBWcmFpICoqZXQqKiBGYXV4IHwgRmF1eCB8CnwgRmF1eCAqKmV0KiogRmF1eCB8IEZhdXggfAp8IFZyYWkgKipvdSoqIFZyYWkgfCBWcmFpIHwKfCBWcmFpICoqb3UqKiBGYXV4IHwgVnJhaSB8CnwgRmF1eCAqKm91KiogRmF1eCB8IEZhdXggfAoKLSBMYSBtw6l0aG9kZSBjbGFzc2lxdWUgY29uc2lzdGUgw6AgYXBwbGlxdWVyIHVuZSBvcMOpcmF0aW9uIGxvZ2lxdWUgZW50cmUgbGVzIGNyb2NoZXRzLCBwYXIgZXhlbXBsZSBgY2hpY291dGVbY2hpY291dGUkQ29kZVRvdXJiaWVyZSA9PSAiQkVBVSIsIF1gCi0gTGEgbcOpdGhvZGUgdGlkeXZlcnNlLCBwbHVzIHByYXRpcXVlIGVuIG1vZGUgcGlwZWxpbmUsIHBhc3NlIHBhciBsYSBmb25jdGlvbiBgZmlsdGVyKClgLCBpLmUuCgpgYGAKY2hpY291dGUgJT4lCmZpbHRlcihDb2RlVG91cmJpZXJlID09ICJCRUFVIikKYGBgCgpDb21iaW5lciBsZSB0b3V0LgoKYGBge3J9CmNoaWNvdXRlICU+JQogIGZpbHRlcihDYV9wb3VyYyA8IDAuNCAmIENvZGVUb3VyYmllcmUgJWluJSBjKCJCRUFVIiwgIk1CIiwgIldUUCIpKSAlPiUKICBzZWxlY3QoY29udGFpbnMoInBvdXJjIikpCmBgYAoKIyMgTGUgZm9ybWF0IGxvbmcgZXQgbGUgZm9ybWF0IGxhcmdlCgpEYW5zIGxlIHRhYmxlYXUgYGNoaWNvdXRlYCwgY2hhcXVlIMOpbMOpbWVudCBwb3Nzw6hkZSBzYSBwcm9wcmUgY29sb25uZS4gU2kgbCdvbiB2b3VsYWl0IG1ldHRyZSBlbiBncmFwaGlxdWUgbGVzIGJveHBsb3QgZGVzIGZhY2V0dGVzIGRlIGNvbmNlbnRyYXRpb25zIGQnYXpvdGUsIGRlIHBob3NwaG9yZSBldCBkZSBwb3Rhc3NpdW0gZGFucyBsZXMgZGlmZsOpcmVudGVzIHRvdXJiacOocmVzLCBpbCBmYXVkcmFpdCBvYnRlbmlyIHVuZSBzZXVsZSBjb2xvbm5lIGRlIGNvbmNlbnRyYXRpb25zLgoKUG91ciBjZSBmYWlyZSwgbm91cyB1dGlsaXNlcm9ucyBsYSBmb25jdGlvbiBgZ2F0aGVyKClgLiBMZSBwcmVtaWVyIGFyZ3VtZW50IGVzdCBsZSBub20gZGUgbGEgY29sb25uZSBkZXMgdmFyaWFibGVzLCBsZSBkZXV4acOobWUgZXN0IGxlIG5vbSBkZSBsYSBub3V2ZWxsZSBjb2xvbm5lIGRlcyB2YWxldXJzLiBMYSBzdWl0ZSBjb25zaXN0ZSDDoCBkw6ljcmlyZSBsZXMgY29sb25uZXMgw6AgaW5jbHVyZSBvdSDDoCBleGNsdWxyZS4gRGFucyBsZSBjYXMgcXVpIHN1aXQsIGonZXhjbHVlIENvZGVUb3VyYmllcmUgZGUgbGEgcmVmb250ZSBqJ3V0aWxpc2UgYHNhbXBsZV9uKClgIHBvdXIgcHLDqXNlbnRlciB1biDDqWNoYW50aWxsb24gZHUgcsOpc3VsdGF0LgoKYGBge3J9CmNoaWNvdXRlX2xvbmcgPC0gY2hpY291dGUgJT4lCiAgc2VsZWN0KENvZGVUb3VyYmllcmUsIE5fcG91cmMsIFBfcG91cmMsIEtfcG91cmMpICU+JQogIGdhdGhlcihrZXkgPSBlbGVtZW50LCB2YWx1ZSA9IGNvbmNlbnRyYXRpb24sIC1Db2RlVG91cmJpZXJlKQpjaGljb3V0ZV9sb25nICU+JSBzYW1wbGVfbigxMCkKYGBgCgpMJ29ww6lyYXRpb24gaW52ZXJzZSBlc3QgYHNwcmVhZCgpYC4gUG91ciBxdWUgY2V0dGUgb3DDqXJhdGlvbiBmb25jdGlvbm5lLCBgc3ByZWFkKClgIGEgYmVzb2luIGQndW5lIGNvbG9ubmUgYXlhbnQgdW4gaWRlbnRpZmlhbnQgdW5pcXVlLgoKYGBge3J9CmNoaWNvdXRlX2xvbmckSUQgPC0gMTpucm93KGNoaWNvdXRlX2xvbmcpCmBgYAoKTm91cyBwb3V2b25zIGVubGV2ZXIgY2V0IGlkZW50aWZpYW50IHVuZSBmb2lzIGwnb3DDqXJhdGlvbiBlZmZlY3R1w6llLgoKYGBge3J9CmNoaWNvdXRlX2xhcmdlIDwtIGNoaWNvdXRlX2xvbmcgJT4lCiAgc3ByZWFkKGtleT1lbGVtZW50LCB2YWx1ZT1jb25jZW50cmF0aW9uLCBmaWxsPTApICU+JQogIHNlbGVjdCgtSUQpCmNoaWNvdXRlX2xhcmdlICU+JSBzYW1wbGVfbigxMCkKYGBgCgpTYW5zIGNyw6llciBkZSBub3V2ZWF1IHRhYmxlYXUsIGlsIGVzdCBwb3NzaWJsZSBkZSBjcsOpZXIgbGUgZ3JhcGhpcXVlIGRhbnMgdW4gc2V1IHBpcGVsaW5lLgoKYGBge3J9CmNoaWNvdXRlICU+JQogIHNlbGVjdChDb2RlVG91cmJpZXJlLCBOX3BvdXJjLCBQX3BvdXJjLCBLX3BvdXJjKSAlPiUKICBnYXRoZXIoa2V5ID0gZWxlbWVudCwgdmFsdWUgPSBjb25jZW50cmF0aW9uLCAtQ29kZVRvdXJiaWVyZSkgJT4lCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IENvZGVUb3VyYmllcmUsIHkgPSBjb25jZW50cmF0aW9uKSkgKwogIGZhY2V0X2dyaWQoZWxlbWVudCB+IC4sIHNjYWxlcyA9ICdmcmVlJykgKwogIGdlb21fYm94cGxvdCgpCmBgYAoKIyMgQ29tYmluZXIgZGVzIHRhYmxlYXV4CgpOb3VzIGF2b25zIGludHJvZHVpdCBwbHVzIGhhdXQgbGEgbm90aW9uIGRlIGJhc2UgZGUgZG9ubsOpZXMuIE5vdXMgdm91ZHJpb25zIHBldXQtw6p0cmUgdXRpbGlzZXIgbGUgY29kZSBkZXMgdG91cmJpw6hyZXMgcG91ciBpbmNsdXJlIGxldXIgbm9tLCBsZSB0eXBlIGQnZXNzYWkgbWVuw6kgw6AgY2VzIHRvdXJiacOocmVzLCBldGMuIEltcG9ydG9ucyBkJ2Fib3JkIGxlIHRhYmxlYXUgZGVzIG5vbXMgbGnDqXMgYXV4IGNvZGVzLgoKYGBge3J9CnRvdXJiaWVyZXMgPC0gcmVhZF9jc3YyKCJkYXRhL2NoaWNvdXRlX3RvdXJiaWVyZXMuY3N2IikKdG91cmJpZXJlcwpgYGAKCk5vdHJlIGluZm9ybWF0aW9uIGVzdCBvcmdhbmlzw6llIGVuIGRldXggdGFibGVhdXgsIGxpw6lzIHBhciBsYSBjb2xvbm5lIGBDb2RlVG91cmJpZXJlYC4gQ29tbWVudCBmdXNpb25uZXIgbCdpbmZvcm1hdGlvbiBwb3VyIHF1J2VsbGUgcHVpc3NlIMOqdHJlIHV0aWxpc8OpZSBkYW5zIHNvbiBlbnNlbWJsZT8gIExhIGZvbmN0aW9uIGBsZWZ0X2pvaW5gIGVmZmVjdHVlIGNldHRlIG9ww6lyYXRpb24gdHlwaXF1ZSBhdmVjIGxlcyBiYXNlcyBkZSBkb25uw6llcy4KCmBgYHtyfQpjaGljb3V0ZV9tZXJnZSA8LSBsZWZ0X2pvaW4oeCA9IGNoaWNvdXRlLCB5ID0gdG91cmJpZXJlcywgYnkgPSAiQ29kZVRvdXJiaWVyZSIpCiMgb3UgYmllbiBjaGljb3V0ZSAlPiUgbGVmdF9qb2luKHkgPSB0b3VyYmllcmVzLCBieSA9ICJDb2RlVG91cmJpZXJlIikKY2hpY291dGVfbWVyZ2UgJT4lIHNhbXBsZV9uKDQpCmBgYAoKRCdhdXRyZXMgdHlwZXMgZGUgam9pbnR1cmVzIHNvbnQgcG9zc2libGVzLCBldCBkw6ljcml0ZXMgZW4gZMOpdGFpbHMgZGFucyBsYSBbZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9qb2luLmh0bWwpLgoKW0dhcnJpY2sgQWRlbi1CdWllXShodHRwczovL3d3dy5nYXJyaWNrYWRlbmJ1aWUuY29tLykgYSBwcsOpcGFyw6kgZGUgW2pvbGllcyBhbmltYXRpb25zXShodHRwczovL2dpc3QuZ2l0aHViLmNvbS9nYWRlbmJ1aWUvMDc3YmNkMjcwMGFjMTI0MWM2NWMzMjQ1ODFhOWY2MTkpIHBvdXIgZMOpY3JpcmUgbGVzIGRpZmbDqXJlbnRzIHR5cGVzIGRlIGpvaW50dXJlcy4KCmBsZWZ0X2pvaW4oeCwgeSlgIGNvbGxlIHkgw6AgeCBzZXVsZW1lbnQgY2UgcXVpIGRhbnMgeSBjb3JyZXNwb25kIMOgIGNlIHF1ZSBsJ29uIHRyb3V2ZSBkYW5zIHguCgohW10oaW1hZ2VzLzA0X2FuaW1hdGVkLWxlZnQtam9pbi5naWYpCgpgcmlnaHRfam9pbih4LCB5KWAgY29sbGUgeSDDoCB4IHNldWxlbWVudCBjZSBxdWkgZGFucyB4IGNvcnJlc3BvbmQgw6AgY2UgcXVlIGwnb24gdHJvdXZlIGRhbnMgeS4KCiFbXShpbWFnZXMvMDRfYW5pbWF0ZWQtcmlnaHQtam9pbi5naWYpIAoKYGlubmVyX2pvaW4oeCwgeSlgIGNvbGxlIHggZXQgeSBlbiBleGNsdWFudCBsZXMgbGlnbmVzIG/DuSBhdSBtb2lucyB1bmUgdmFyaWFibGUgZGUgam9pbnQgZXN0IGFic2VudGUgZGFucyB4IGV0IHkuCgohW10oaW1hZ2VzLzA0X2FuaW1hdGVkLWlubmVyLWpvaW4uZ2lmKSAKCmBmdWxsX2pvaW4oeCwgeSlgZ2FyZGUgdG91dGVzIGxlcyBsaWduZXMgZXQgbGVzIGNvbG9ubmVzIGRlIHggZXQgeS4KCiFbXShpbWFnZXMvMDRfYW5pbWF0ZWQtZnVsbC1qb2luLmdpZikgCgojIyBPcMOpcmF0aW9ucyBzdXIgbGVzIHRhYmxlYXV4CgpMZXMgdGFibGVhdXggcGV1dmVudCDDqnRyZSBzZWdtZW50w6lzIGVuIMOpbMOpbWVudHMgc3VyIGxlc3F1ZWxzIG9uIGNhbGN1bGVyYSBjZSBxdWkgbm91cyBjaGFudGUuCgpPbiBwb3VycmFpdCB2b3Vsb2lyIG9idGVuaXI6CgotIGxhIHNvbW1lIGF2ZWMgbGEgZnVuY3Rpb24gYHN1bSgpYAotIGxhIG1veWVubmUgYXZlYyBsYSBmdW5jdGlvbiBgbWVhbigpYCBvdSBsYSBtw6lkaWFuZSBhdmVjIGxhIGZvbmN0aW9uIGBtZWRpYW4oKWAKLSBsJ8OpY2FydC10eXBlIGF2ZWMgbGEgZnVuY3Rpb24gYHNkKClgCi0gbGVzIG1heGltdW0gZXQgbWluaW11bSBhdmVjIGxlcyBmb25jdGlvbnMgYG1pbigpYCBldCBgbWF4KClgCi0gdW4gZMOpY29tcHRlIGQnb2NjdXJlbmNlIGF2ZWMgbGEgZm9uY3Rpb24gYG4oKWAgb3UgYGNvdW50KClgCgpQYXIgZXhlbXBsZSwKCmBgYHtyfQptZWFuKGNoaWNvdXRlJFJlbmRlbWVudF9nXzVtMiwgbmEucm0gPSBUUlVFKQpgYGAKCioqRW4gbW9kZSBjbGFzc2lxdWUqKiwgcG91ciBlZmZlY3R1ZXIgZGVzIG9ww6lyYXRpb25zIHN1ciBkZXMgdGFibGVhdXgsIG9uIHV0aWxpc2VyYSBsYSBmb25jdGlvbiBgYXBwbHkoKWAuIENldHRlIGZvbmN0aW9uIHByZW5kLCBjb21tZSBhcmd1bWVudHMsIGxlIHRhYmxlYXUsIGwnYXhlIChvcMOpcmF0aW9uIHBhciBsaWduZSA9IDEsIG9ww6lyYXRpb24gcGFyIGNvbG9ubmUgPSAyKSwgcHVpcyBsYSBmb25jdGlvbiDDoCBhcHBsaXF1ZXIuCgpgYGB7cn0KYXBwbHkoY2hpY291dGUgJT4lIHNlbGVjdChjb250YWlucygicG91cmMiKSksIDIsIG1lYW4pCmBgYAoKTGVzIG9ww6lyYXRpb24gcGV1dmVudCBhdXNzaSDDqnRyZSBlZmZlY3R1w6llcyBwYXIgbGlnbmUsIHBhciBleGVtcGxlIHVuZSBzb21tZSAoamUgZ2FyZGUgc2V1bGVtZW50IGxlcyAxMCBwcmVtaWVycyByw6lzdWx0YXRzKS4KCmBgYHtyfQphcHBseShjaGljb3V0ZSAlPiUgc2VsZWN0KGNvbnRhaW5zKCJwb3VyYyIpKSwgMSwgc3VtKVsxOjEwXQpgYGAKCkxhIGZvbmN0aW9uIMOgIGFwcGxpcXVlciBwZXV0IMOqdHJlIHBlcnNvbm5hbGlzw6llLCBwYXIgZXhlbXBsZToKCmBgYHtyfQphcHBseShjaGljb3V0ZSAlPiUgc2VsZWN0KGNvbnRhaW5zKCJwb3VyYyIpKSwgMiwKICAgICAgZnVuY3Rpb24oeCkgKHByb2QoeCkpXigxL2xlbmd0aCh4KSkpCmBgYAoKVm91cyByZWNvbm5haXNzZXogY2V0dGUgZm9uY3Rpb24/IEMnw6l0YWl0IGxhIG1veWVubmUgZ8Opb23DqXRyaXF1ZSAobGEgZm9uY3Rpb24gYHByb2QoKWAgw6l0YW50IGxlIHByb2R1aXQgZCd1biB2ZWN0ZXVyKS4KCioqRW4gbW9kZSB0aWR5dmVyc2UqKiwgb24gYXVyYSBiZXNvaW4gcHJpbmNpcGFsZW1lbnQgZGVzIGZvbmN0aW9uIHN1aXZhbnRlczoKCiogYGdyb3VwX2J5KClgIHBvdXIgZWZmZWN0dWVyIGRlcyBvcMOpcmF0aW9ucyBwYXIgZ3JvdXBlLCBsJ29wcsOpcmF0aW9uIGBncm91cF9ieSgpYCBzw6lwYXJlIGxlIHRhYmxlYXUgZW4gcGx1c2lldXJzIHBldGl0cyB0YWJsZWF1eCwgZW4gYXR0ZW5kYW50IGRlIGxlcyByZWNvbWJpbmVyLiBDJ2VzdCB1biBwZXUgbCfDqXF1aXZhbGVudCBkZXMgZmFjZXR0ZXMgZW4gZ2dwbG90Mi4uLgoqIGBzdW1tYXJpc2UoKWAgcG91ciByw6lkdWlyZSBwbHVzaWV1cnMgdmFsZXVycyBlbiB1bmUgc2V1bGUsIGlsIGFwcGxpcXVlIHVuIGNhbGN1bCBzdXIgbGUgdGFibGVhdSBvdSBzJ2lsIHkgYSBsaWV1IHN1ciBjaGFxdWUgcGV0aXQgdGFibGVhdSBzZWdtZW50w6kuIElsIGVuIGV4aXN0ZSBxdWVscXVlcyB2YXJpYW50ZXMuCiAgICArIGBzdW1tYXJpc2VfYWxsKClgIGFwcGxpcXVlIGxhIGZvbmN0aW9uIMOgIHRvdXRlcyBsZXMgY29sb25uZXMKICAgICsgYHN1bW1hcmlzZV9hdCgpYCBhcHBsaXF1ZSBsYSBmb25jdGlvbiBhdXggY29sb25uZXMgc3DDqWNpZmnDqWVzCiAgICArIGBzdW1tYXJpc2VfaWYoKWAgYXBwbGlxdWUgbGEgZm9uY3Rpb24gYXV4IGNvbG9ubmVzIHF1aSByZXNvcnRlbnQgY29tbWUgYFRSVUVgIHNlbG9uIHVuZSBvcMOpcmF0aW9uIGJvb2zDqWVubmUKKiBgbXV0YXRlKClgIHBvdXIgYWpvdHVlciB1bmUgbm91dmVsbGUgY29sb25uZQogICAgKyBTaSBsJ29uIGTDqXNpcmUgYWpvdXRlciB1bmUgY29sb25uZSDDoCB1biB0YWJsZWF1LCBwYXIgZXhlbXBsZSBsZSBzb21tYWlyZSBjYWxjdWzDqSBhdmVjIGBzdW1tYXJpc2UoKWAuIMOAIGwnaW52ZXJzZSwgbGEgZm9uY3Rpb24gYHRyYW5zbXV0ZSgpYCByZXRvdXJuZXJhIHNldWxlbWVudCBsZSByw6lzdWx0YXQsIHNhbnMgbGUgdGFibGVhdSDDoCBwYXJ0aXIgZHVxdWVsIGlsIGEgw6l0w6kgY2FsY3Vsw6kuIERlIG3Dqm1lIHF1ZSBgc3VtbWFyaXNlKClgLCBgbXV0YXRlKClgIGV0IGB0cmFuc211dGUoKWAgcG9zc8OoZGVudCBsZXVycyDDqXF1aXZhbGVudHMgYF9hbGwoKWAsIGBfYXQoKWAgZXQgYF9pZigpYC4KKiBgYXJyYW5nZSgpYCBwb3VyIHLDqW9yZG9ubmVyIGxlIHRhYmxlYXUKICAgICsgT24gYSBkw6lqw6AgY291dmVydCBgYXJyYW5nZSgpYCBkYW5zIGxlIGNoYXBpdHJlIDMuIFJhcHBlbG9ucyBxdWUgY2V0dGUgZm9uY3Rpb24gbidlc3QgcGFzIHVuZSBvcMOpcmF0aW9uIHN1ciB1biB0YWJsZWF1LCBtYWlzIHBsdXTDtHQgdW4gY2hhbmdlbWVudCBkJ2FmZmljaGFnZSBlbiBjaGFuZ2VhbnQgbCdvcmRyZSBkJ2FwcGFyaXRpb24gZGVzIGRvbm7DqWVzLgoKQ2VzIG9ww6lyYXRpb25zIHNvbnQgZMOpY3JpdGVzIGRhbnMgbCdhaWRlLW3DqW1vaXJlIFsqRGF0YSBUcmFuc2Zvcm1hdGlvbiBDaGVhdCBTaGVldCpdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvZGF0YS10cmFuc2Zvcm1hdGlvbi5wZGYpLgoKWyFbXShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMS9kYXRhLXRyYW5zZm9ybWF0aW9uLWNoZWF0c2hlZXQtNjAweDQ2NC5wbmcpXShodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9yYXcvbWFzdGVyL2RhdGEtdHJhbnNmb3JtYXRpb24ucGRmKQoKQWlkZS1tw6ltb2lyZSBkZSBkcGx5ciwgc291cmNlOiBodHRwczovL3d3dy5yc3R1ZGlvLmNvbS9yZXNvdXJjZXMvY2hlYXRzaGVldHMvCgpQb3VyIGVmZmVjdHVlciBkZXMgc3RhdGlzdGlxdWVzIHBhciBjb2xvbm5lLCBvbiB1dGlsaXNlcmEgYHN1bW1hcmlzZV9hbGwoKWAgw6l0YW50IGRvbm7DqWUgcXVlIGwnb24gZMOpc2lyZSB1biBzb21tYWlyZSBzdXIgdG91dGVzIGxlcyB2YXJpYWJsZXMgc8OpbGVjdGlvbm7DqWVzLiBQb3VyIHNww6ljaWZpZXIgcXVlIGwnb24gZMOpc2lyZSBsYSBtb3llbm5lIGV0IGwnw6ljYXJ0LXR5cGUgb24gaW5zY3JpdCBsZXMgbm9tcyBkZXMgZm9uY3Rpb25zIGRhbnMgYGZ1bnMoKWAuCgpgYGB7cn0KY2hpY291dGUgJT4lCiAgc2VsZWN0KGNvbnRhaW5zKCJwb3VyYyIpKSAlPiUKICBzdW1tYXJpc2VfYWxsKGZ1bnMobWVhbiwgc2QpKQpgYGAKCk9uIHV0aWxpc2VyYSBgZ3JvdXBfYnkoKWAgcG91ciBzZWdtZW50ZXIgbGUgdGFibGVhdSwgZXQgYWluc2kgb2J0ZW5pciBkZXMgc3RhdGlzdGlxdWVzIHBvdXIgY2hhcXVlIGdyb3VwZS4KCmBgYHtyfQpjaGljb3V0ZSAlPiUKICBncm91cF9ieShDb2RlVG91cmJpZXJlKSAlPiUKICBzZWxlY3QoY29udGFpbnMoInBvdXJjIikpICU+JQogIHN1bW1hcmlzZV9hbGwoZnVucyhtZWFuLCBzZCkpCmBgYAoKUG91ciBvYnRlbmlyIGRlcyBzdGF0aXN0aXF1ZXMgw6AgY2hhcXVlIGxpZ25lLCBtaWV1eCB2YXV0IHV0aWxpc2VyIGBhcHBseSgpYCwgdGVsIHF1ZSB2dSBwcsOpY8OpZGVtbWVudC4gTGUgcG9pbnQsIGAuYCwgcmVwcsOpc2VudGUgbGUgdGFibGVhdSBkYW5zIGxhIGZvbmN0aW9uLgoKYGBge3J9CmNoaWNvdXRlICU+JQogIHNlbGVjdChjb250YWlucygicG91cmMiKSkgJT4lCiAgYXBwbHkoLiwgMSwgc3VtKQpgYGAKClJldmVub25zIMOgIG5vdHJlIHRhYmxlYXUgZGVzIGVzcGVjZXMgbWVhbmPDqWVzLgoKYGBge3Igd2FybmluZz1GQUxTRX0KZXNwZWNlc19tZW5hY2VlcyA8LSByZWFkX2NzdignZGF0YS9XSUxEX0xJRkVfMTYwODIwMTgwNTE3MzI3NTQuY3N2JykKYGBgCgpOb3VzIGF2aW9ucyBleMOpY3V0w6kgbGUgcGlwZWxpbmUgc3VpdmFudC4KCmBgYHtyfQplc3BlY2VzX21lbmFjZWVzICU+JQogIGZpbHRlcihJVUNOID09ICdDUklUSUNBTCcpICU+JQogIHNlbGVjdChDb3VudHJ5LCBWYWx1ZSkgJT4lCiAgZ3JvdXBfYnkoQ291bnRyeSkgICU+JQogIHN1bW1hcmlzZShuX2NyaXRpY2FsX3NwZWNpZXMgPSBzdW0oVmFsdWUpKSAlPiUKICBhcnJhbmdlKGRlc2Mobl9jcml0aWNhbF9zcGVjaWVzKSkgJT4lCiAgdG9wX24oMTApCmBgYAoKQ2UgcGlwZWxpbmUgY29uc2lzdGFpdCDDoDoKCmBgYApwcmVuZHJlIGxlIHRhYmxlYXUgZXNwZWNlc19tZW5hY2VlcywgcHVpcwogIGZpbHRyZXIgcG91ciBuJ29idGVuaXIgcXVlIGxlcyBlc3DDqGNlcyBjcml0aXF1ZXMsIHB1aXMKICBzw6lsZWN0aW9ubmVyIGxlcyBjb2xvbm5lcyBkZXMgcGF5cyBldCBkZXMgdmFsZXVycyAobm9tYnJlIGQnZXNww6hjZXMpLCBwdWlzCiAgc2VnbWVudGVyIGxlIHRhYmxlYXV4IGVuIHBsdXNpZXVycyB0YWJsZWF1eCBzZWxvbiBsZSBwYXlzLCBwdWlzCiAgYXBwbGlxdWVyIGxhIGZvbmN0aW9uIHN1bSBwb3VyIGNoYWN1biBkZSBjZXMgcGV0aXRzIHRhYmxlYXV4IChwdWlzIGRlIHJlY29tYmluZXIgY2VzIHNvbW1haXJlcyksIHB1aXMKICB0cmllciBsZXMgcGF5cyBlbiBub21icmUgZMOpY3JvaXNzYW50IGRlIGTDqWNvbXB0ZSBkJ2VzcMOoY2VzLCBwdWlzCiAgYWZmaWNoZXIgbGUgdG9wIDEwCmBgYAoKIyMgRXhlbXBsZSAoZGlmZmljaWxlKQoKUG91ciByZXZlbmlyIMOgIG5vdHJlIHRhYmxlYXUgYGNoaWNvdXRlYCwgaW1hZ2luZXogcXVlIHZvdXMgYXZpZXogdW5lIHN0YXRpb24gbcOpdMOpbyAoc3RhdGlvbl9BKSBzaXR1w6llIGF1eCBjb29yZG9ubsOpZXMgKDQ5MDY0MCwgNTcwMjQ1MykgZXQgcXVlIHZvdXMgZMOpc2lyaWV6IGNhbGN1bGVyIGxhIGRpc3RhbmNlIGVudHJlIGwnb2JzZXJ2YXRpb24gZXQgbGEgc3RhdGlvbi4gUHJlbmV6IGR1IHRlbXBzIHBvdXIgcsOpZmzDqWNoaXIgw6AgbGEgbWFuacOocmUgZG9udCB2b3VzIHByb2PDqWRlcmV6Li4uIAoKT24gcG91cnJhIGNyw6llciB1bmUgZm9uY3Rpb24gcXVpIG1lc3VyZSBsYSBkaXN0YW5jZSBlbnRyZSB1biBwb2ludCB4LCB5IGV0IGxlcyBjb29yZG9ubsOpZXMgZGUgbGEgc3RhdGlvbiBBLi4uCgpgYGB7cn0KZGlzdF9zdGF0aW9uX0EgPC0gZnVuY3Rpb24gKHgsIHkpIHsKICByZXR1cm4oc3FydCgoeCAtIDQ5MDY0MCleMiArICh5IC0gNTcwMjQ1MyleMikpCn0KYGBgCgouLi4gcHVpcyBham91dGVyIHVuZSBjb2xvbm5lIGF2ZWMgbXV0YXRlIGdyw6JjZSDDoCB1bmUgZm9uY3Rpb24gcHJlbmFudCBsZXMgYXJndW1lbnRzIHggZXQgeSBzcMOpY2lmacOpcy4KCmBgYHtyfQpjaGljb3V0ZSAlPiUKICBtdXRhdGUoZGlzdCA9IGRpc3Rfc3RhdGlvbl9BKHggPSBMb25naXR1ZGVfbSwgeT0gTGF0aXR1ZGVfbSkpICU+JQogIHNlbGVjdChJRCwgQ29kZVRvdXJiaWVyZSwgTG9uZ2l0dWRlX20sIExhdGl0dWRlX20sIGRpc3QpICU+JQogIHRvcF9uKDEwKQpgYGAKCgpOb3VzIHBvdXJyaW9ucyBwcm9jw6lkZXIgZGUgbGEgbcOqbWUgbWFuacOocmUgcG91ciBmdXNpb25uZXIgZGVzIGRvbm7DqWVzIGNsaW1hdGlxdWVzLiBMZSB0YWJsZWF1IGBjaGljb3V0ZWAgbmUgcG9zc8OoZGUgcGFzIGQnaW5kaWNhdGV1cnMgY2xpbWF0aXF1ZXMsIG1haXMgaWwgZXN0IHBvc3NpYmxlIGRlIGxlcyBzb3V0aXJlciBkZSBzdGF0aW9ucyBtw6l0w6lvcyBwbGFjw6llcyBwcsOocyBkZXMgc2l0ZS4gQ2VzIGRvbm7DqWVzIG5lIHNvbnQgcGFzIGRpc3BvbmlibGVzIHBvdXIgbGUgdGFibGVhdSBkZSBsYSBjaGljb3V0w6ksIGFsb3JzIGondXRpbGlzZXJhaSBkZXMgZG9ubsOpZXMgZmljdGl2ZXMgcG91ciBsJ2V4ZW1wbGUuCgpWb2ljaSBjZSBxdWkgcG91cnJhaXQgw6p0cmUgZmFpdC4KCjEuIENyw6llciB1biB0YWJsZWF1IGRlcyBzdGF0aW9ucyBtw6l0w6lvIGFpbnNpIHF1ZSBkZXMgaW5kaWNlcyBtw6l0w6lvIGFzc29jacOpcyDDoCBjZXMgc3RhdGlvbnMuCjIuIExpZXIgY2hhcXVlIHNpdGUgw6AgdW5lIHN0YXRpb24gKMOgIGxhIG1haW4gb8O5IHNlbG9uIGxhIHBsdXMgcGV0aXRlIGRpc3RhbmNlIGVudHJlIGxlIHNpdGUgZXQgbGEgc3RhdGlvbikuCjMuIEZ1c2lvbm5lciBsZXMgaW5pY2VzIGNsaW1hdGlxdWVzIGF1eCBzaXRlcywgcHVpcyBsZXMgc2l0ZXMgYXV4IG1lc3VyZXMgZGUgcmVuZGVtZW50LgoKQ2VzIG9ww6lyYXRpb25zIGRlbWFuZGVudCBoYWJpdHVlbGxlbWVudCBkdSB0w6J0b25uZW1lbnQuIElsIHNlcmFpdCBzdXJwcmVuYW50IHF1ZSBtw6ptZSB1bmUgcGVyc29ubmUgZXhww6lyaW1lbnTDqWUgc29pdCBlbiBtZXN1cmUgZGUgY29tcGlsZXIgY2VzIG9ww6lyYXRpb25zIHNhbnMgb2J0ZW5pciBkZSBtZXNzYWdlIGQnZXJyZXVyLCBldCByZXRyYXZhaWxsZXIganVzcXUnw6Agb2J0ZW5pciBsZSByw6lzdWx0YXQgc291aGFpdMOpLiBMJ29iamVjdGlmIGRlIGNldHRlIHNlY3Rpb24gZXN0IGRlIHZvdXMgcHLDqXNlbnTDqSB1biBmbHV4IGRlIHRyYXZhaWwgcXVlIHZvdXMgcG91cnJpZXogw6p0cmUgYW1lbsOpcyDDoCBlZmZlY3R1ZXIgZXQgZGUgZm91cm5pciBxdWVscXVlcyDDqWzDqW1lbnRzIG5vdXZlYXUgcG91ciBtZW5lciDDoCBiaWVuIHVuZSBvcMOpcmF0aW9uLiBJbCBwZXV0IMOqdHJlIGZydXN0YW50IGRlIG5lIHBhcyBzYWlzaXIgdG91dGVzIGxlcyBvcMOpcmF0aW9uczogcGFzc2V6IMOgIHRyYXZlcnMgY2V0dGUgc2VjdGlvbiBzYW5zIGp1Z2VtZW50LiBTaSB2b3VzIGRldmV6IHZvdXMgZnJvdHRlciDDoCBwcm9ibMOobWUgc2VtYmxhYmxlLCB2b3VzIHNhdXJleiBxdWUgdm91cyB0cm91dmVyZXogZGFucyBjZSBtYW51ZWwgdW5lIHJlY2V0dGUgaW50w6lyZXNzYW50ZS4KCmBgYHtyfQpzdGF0aW9ucyA8LSBkYXRhLmZyYW1lKFN0YXRpb24gPSBjKCdBJywgJ0InLCAnQycpLAogICAgICAgICAgICAgICAgICAgICAgIExvbmdpdHVkZV9tID0gYyg0OTA2NDAsIDQ4NDg3MCwgNDg1OTI5KSwKICAgICAgICAgICAgICAgICAgICAgICBMYXRpdHVkZV9tID0gYyg1NzAyNDUzLCA1NzAxODcwLCA1Njk2NDIxKSwgCiAgICAgICAgICAgICAgICAgICAgICAgdF9tb3lfQyA9IGMoMTMuOCwgMTguMiwgMTYuMzApLAogICAgICAgICAgICAgICAgICAgICAgIHByZWNfdG90X21tID0gYyg2ODcsIDcxNCwgNzMyKSkKc3RhdGlvbnMKYGBgCgpMYSBmb25jdGlvbiBzdWl2YW50ZSBjYWxjdWxlIGxhIGRpc3RhbmNlIGVudHJlIGRlcyBjb29yZG9ubsOpZXMgeCBldCB5IGV0IGNoYXF1ZSBzdGF0aW9uIGQndW4gdGFibGVhdSBkZSBzdGF0aW9ucywgcHVpcyByZXRvdXJuZSBsZSBub20gZGUgbGEgc3RhdGlvbiBkb250IGxhIGRpc3RhbmNlIGVzdCBsYSBtb2luZHJlLgoKYGBge3J9CmRpc3Rfc3RhdGlvbiA8LSBmdW5jdGlvbiAoeCwgeSwgc3RhdGlvbnNfZGYpIHsKICAgICMgc3RhdGlvbnMgZXN0IGxlIHRhYmxlYXUgZGVzIHN0YXRpb25zIMOgIHRyb2lzIGNvbG9ubmVzCiAgICAjIDFpZXJlOiBub20gZGUgbGEgc3RhdGlvbgogICAgIyAyaWVtZTogbG9uZ2l0dWRlCiAgICAjIDNpZW1lOiBsYXRpdHVkZQogICAgZGlzdGFuY2UgPC0gYygpCiAgICBmb3IgKGkgaW4gMTpucm93KHN0YXRpb25zKSkgewogICAgICAgIGRpc3RhbmNlW2ldIDwtIHNxcnQoKHggLSBzdGF0aW9uc1tpLCAyXSleMiArICh5IC0gc3RhdGlvbnNbaSwgM10pXjIpCiAgICB9CiAgICBub21fc3RhdGlvbiA8LSBhcy5jaGFyYWN0ZXIoc3RhdGlvbnMkU3RhdGlvblt3aGljaC5taW4oZGlzdGFuY2UpXSkKICAgIHJldHVybihub21fc3RhdGlvbikKfQpgYGAKClRlc3RvbnMgbGEgZm9uY3Rpb24gYXZlYyBkZXMgY29vcmRvbm7DqWVzLgoKYGBge3J9CmRpc3Rfc3RhdGlvbih4ID0gNDU5ODc1LCB5ID0gNTcwMTk4OCwgc3RhdGlvbnNfZGYgPSBzdGF0aW9ucykKYGBgCgpOb3VzIGFwcGxpcXVvbnMgY2V0dGUgZm9uY3Rpb24gw6AgdG91dGVzIGxlcyBsaWduZXMgZHUgdGFibGVhdSwgcHVpcyBlbiByZXRvdXJub25zIHVuIMOpY2hhbnRpbGxvbi4KCmBgYHtyfQpjaGljb3V0ZSAlPiUKICAgIHJvd3dpc2UoKSAlPiUKICAgIG11dGF0ZShTdGF0aW9uID0gZGlzdF9zdGF0aW9uKHggPSBMb25naXR1ZGVfbSwgeSA9IExhdGl0dWRlX20sIHN0YXRpb25zX2RmID0gc3RhdGlvbnMpKSAlPiUKICAgIHNlbGVjdChJRCwgQ29kZVRvdXJiaWVyZSwgTG9uZ2l0dWRlX20sIExhdGl0dWRlX20sIFN0YXRpb24pICU+JQogICAgc2FtcGxlX24oMTApCmBgYAoKQ2VsYSBzZW1ibGUgZm9uY3Rpb25uZXIuIE9uIHBldXQgeSBham91dGVyIHVuIGBsZWZ0X2pvaW4oKWAgcG91ciBqb2luZHJlIGxlcyBkb25uw6llcyBtw6l0w6lvIGF1IHRhYmxlYXUgcHJpbmNpcGFsLgoKYGBge3J9CmNoaWNvdXRlX3dlYXRoZXIgPC0gY2hpY291dGUgJT4lCiAgICByb3d3aXNlKCkgJT4lCiAgICBtdXRhdGUoU3RhdGlvbiA9IGRpc3Rfc3RhdGlvbih4ID0gTG9uZ2l0dWRlX20sIHkgPSBMYXRpdHVkZV9tLCBzdGF0aW9uc19kZiA9IHN0YXRpb25zKSkgJT4lCiAgICBsZWZ0X2pvaW4oeSA9IHN0YXRpb25zLCBieSA9ICJTdGF0aW9uIikKY2hpY291dGVfd2VhdGhlciAlPiUgc2FtcGxlX24oMTApCmBgYAoKIyMgRXhwb3J0ZXIgdW4gdGFibGVhdQoKU2ltcGxlbWVudCBhdmVjIGB3cml0ZV9jc3YoKWAuCgpgYGB7cn0Kd3JpdGVfY3N2KGNoaWNvdXRlX3dlYXRoZXIsICJkYXRhL2NoaWNvdXRlX3dlYXRoZXIuY3N2IikKYGBgCgojIyBBbGxlciBwbHVzIGxvaW4gZGFucyBsZSB0aWR5dmVyc2UKCkxlIGxpdnJlIFtSIGZvciBEYXRhIFNjaWVuY2VdKGh0dHA6Ly9yNGRzLmhhZC5jby5ueiksIGRlIEdhcnJldHQgR3JvbGVtdW5kIGV0IEhhZGxleSBXaWNraGFtLCBlc3QgdW4gaW5jb250b3VybmFibGUuCgpbPGltZyBzcmM9Imh0dHA6Ly9yNGRzLmhhZC5jby5uei9jb3Zlci5wbmciIHdpZHRoPTIwMD5dKGh0dHA6Ly9yNGRzLmhhZC5jby5ueikKCgojIFLDqWbDqXJlbmNlcwoKUGFyZW50IEwuRS4sIFBhcmVudCwgUy7DiS4sIEhlcmJlcnQtR2VudGlsZSwgVi4sIE5hZXNzLCBLLiBldCAgTGFwb2ludGUsIEwuIDIwMTMuIE1pbmVyYWwgQmFsYW5jZSBQbGFzdGljaXR5IG9mIENsb3VkYmVycnkgKFJ1YnVzIGNoYW1hZW1vcnVzKSBpbiBRdWViZWMtTGFicmFkb3IgQm9ncy4gQW1lcmljYW4gSm91cm5hbCBvZiBQbGFudCBTY2llbmNlcywgNCwgMTUwOC0xNTIwLiBET0k6IDEwLjQyMzYvYWpwcy4yMDEzLjQ3MTgz